Skip to content

Commit 73dc281

Browse files
Merge branch 'main' into 35C4n0r/codex-exorcism
2 parents 8ea4c77 + 4d96be0 commit 73dc281

3 files changed

Lines changed: 136 additions & 7 deletions

File tree

registry/coder/modules/claude-code/README.md

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ Install and configure the [Claude Code](https://docs.anthropic.com/en/docs/agent
1313
```tf
1414
module "claude-code" {
1515
source = "registry.coder.com/coder/claude-code/coder"
16-
version = "5.0.0"
16+
version = "5.1.0"
1717
agent_id = coder_agent.main.id
1818
anthropic_api_key = "xxxx-xxxxx-xxxx"
1919
}
@@ -47,7 +47,7 @@ locals {
4747
4848
module "claude-code" {
4949
source = "registry.coder.com/coder/claude-code/coder"
50-
version = "5.0.0"
50+
version = "5.1.0"
5151
agent_id = coder_agent.main.id
5252
workdir = local.claude_workdir
5353
anthropic_api_key = "xxxx-xxxxx-xxxx"
@@ -78,7 +78,7 @@ resource "coder_app" "claude" {
7878
```tf
7979
module "claude-code" {
8080
source = "registry.coder.com/coder/claude-code/coder"
81-
version = "5.0.0"
81+
version = "5.1.0"
8282
agent_id = coder_agent.main.id
8383
workdir = "/home/coder/project"
8484
enable_ai_gateway = true
@@ -102,7 +102,7 @@ This example shows version pinning, a pre-installed binary path, a custom model,
102102
```tf
103103
module "claude-code" {
104104
source = "registry.coder.com/coder/claude-code/coder"
105-
version = "5.0.0"
105+
version = "5.1.0"
106106
agent_id = coder_agent.main.id
107107
workdir = "/home/coder/project"
108108
@@ -166,7 +166,7 @@ Downstream `coder_script` resources can wait for this module's install pipeline
166166
```tf
167167
module "claude-code" {
168168
source = "registry.coder.com/coder/claude-code/coder"
169-
version = "5.0.0"
169+
version = "5.1.0"
170170
agent_id = coder_agent.main.id
171171
workdir = "/home/coder/project"
172172
anthropic_api_key = "xxxx-xxxxx-xxxx"
@@ -252,7 +252,7 @@ resource "coder_env" "bedrock_api_key" {
252252
253253
module "claude-code" {
254254
source = "registry.coder.com/coder/claude-code/coder"
255-
version = "5.0.0"
255+
version = "5.1.0"
256256
agent_id = coder_agent.main.id
257257
workdir = "/home/coder/project"
258258
model = "global.anthropic.claude-sonnet-4-5-20250929-v1:0"
@@ -309,7 +309,7 @@ resource "coder_env" "google_application_credentials" {
309309
310310
module "claude-code" {
311311
source = "registry.coder.com/coder/claude-code/coder"
312-
version = "5.0.0"
312+
version = "5.1.0"
313313
agent_id = coder_agent.main.id
314314
workdir = "/home/coder/project"
315315
model = "claude-sonnet-4@20250514"
@@ -341,6 +341,34 @@ module "claude-code" {
341341
> [!NOTE]
342342
> For additional Vertex AI configuration options (model selection, token limits, region overrides, etc.), see the [Claude Code Vertex AI documentation](https://docs.claude.com/en/docs/claude-code/google-vertex-ai).
343343
344+
### Telemetry export (OpenTelemetry)
345+
346+
Claude Code can emit OpenTelemetry metrics and events covering token usage, tool calls, session lifecycle, and errors (see the [monitoring docs](https://docs.anthropic.com/en/docs/claude-code/monitoring-usage)). Set `telemetry.enabled = true` and point `otlp_endpoint` at your OTLP collector.
347+
348+
The module automatically tags every span and metric with `coder.workspace_id`, `coder.workspace_name`, `coder.workspace_owner`, and `coder.template_name` via `OTEL_RESOURCE_ATTRIBUTES`, so Claude Code telemetry can be joined directly against Coder's [audit logs](https://coder.com/docs/admin/security/audit-logs) and `exectrace` records on `workspace_id`.
349+
350+
```tf
351+
module "claude-code" {
352+
source = "registry.coder.com/coder/claude-code/coder"
353+
version = "5.1.0"
354+
agent_id = coder_agent.main.id
355+
workdir = "/home/coder/project"
356+
anthropic_api_key = "xxxx-xxxxx-xxxx"
357+
358+
telemetry = {
359+
enabled = true
360+
otlp_endpoint = "http://otel-collector.observability:4317"
361+
otlp_protocol = "grpc"
362+
otlp_headers = {
363+
authorization = "Bearer ${var.otel_token}"
364+
}
365+
resource_attributes = {
366+
"service.name" = "claude-code"
367+
}
368+
}
369+
}
370+
```
371+
344372
## Troubleshooting
345373

346374
If you encounter any issues, check the log files in the `~/.coder-modules/coder/claude-code/logs` directory within your workspace for detailed information.

registry/coder/modules/claude-code/main.test.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,4 +435,41 @@ describe("claude-code", async () => {
435435
]);
436436
expect(resp.stdout.trim()).toBe("ABSENT");
437437
});
438+
439+
test("telemetry-otel", async () => {
440+
const { coderEnvVars } = await setup({
441+
moduleVariables: {
442+
telemetry: JSON.stringify({
443+
enabled: true,
444+
otlp_endpoint: "http://otel-collector:4317",
445+
otlp_protocol: "grpc",
446+
otlp_headers: { authorization: "Bearer test-token" },
447+
resource_attributes: { "service.name": "claude-code" },
448+
}),
449+
},
450+
});
451+
expect(coderEnvVars["CLAUDE_CODE_ENABLE_TELEMETRY"]).toBe("1");
452+
expect(coderEnvVars["OTEL_EXPORTER_OTLP_ENDPOINT"]).toBe(
453+
"http://otel-collector:4317",
454+
);
455+
expect(coderEnvVars["OTEL_EXPORTER_OTLP_PROTOCOL"]).toBe("grpc");
456+
expect(coderEnvVars["OTEL_EXPORTER_OTLP_HEADERS"]).toBe(
457+
"authorization=Bearer test-token",
458+
);
459+
const attrs = coderEnvVars["OTEL_RESOURCE_ATTRIBUTES"];
460+
expect(attrs).toContain("coder.workspace_id=");
461+
expect(attrs).toContain("coder.workspace_name=");
462+
expect(attrs).toContain("coder.workspace_owner=");
463+
expect(attrs).toContain("coder.template_name=");
464+
expect(attrs).toContain("service.name=claude-code");
465+
});
466+
467+
test("telemetry-disabled-by-default", async () => {
468+
const { coderEnvVars } = await setup();
469+
expect(coderEnvVars["CLAUDE_CODE_ENABLE_TELEMETRY"]).toBeUndefined();
470+
expect(coderEnvVars["OTEL_EXPORTER_OTLP_ENDPOINT"]).toBeUndefined();
471+
expect(coderEnvVars["OTEL_EXPORTER_OTLP_PROTOCOL"]).toBeUndefined();
472+
expect(coderEnvVars["OTEL_EXPORTER_OTLP_HEADERS"]).toBeUndefined();
473+
expect(coderEnvVars["OTEL_RESOURCE_ATTRIBUTES"]).toBeUndefined();
474+
});
438475
});

registry/coder/modules/claude-code/main.tf

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,18 @@ variable "enable_ai_gateway" {
118118
}
119119
}
120120

121+
variable "telemetry" {
122+
type = object({
123+
enabled = optional(bool, false)
124+
otlp_endpoint = optional(string, "")
125+
otlp_protocol = optional(string, "http/protobuf")
126+
otlp_headers = optional(map(string), {})
127+
resource_attributes = optional(map(string), {})
128+
})
129+
default = {}
130+
description = "Configure Claude Code OpenTelemetry export. When enabled, sets CLAUDE_CODE_ENABLE_TELEMETRY and the standard OTEL_EXPORTER_OTLP_* environment variables. Coder workspace identifiers (coder.workspace_id, coder.workspace_name, coder.workspace_owner, coder.template_name) are automatically appended to OTEL_RESOURCE_ATTRIBUTES so Claude Code telemetry can be joined with Coder audit and exectrace logs."
131+
}
132+
121133
resource "coder_env" "claude_code_oauth_token" {
122134
count = var.claude_code_oauth_token != "" ? 1 : 0
123135
agent_id = var.agent_id
@@ -163,6 +175,58 @@ resource "coder_env" "anthropic_base_url" {
163175
value = "${data.coder_workspace.me.access_url}/api/v2/aibridge/anthropic"
164176
}
165177

178+
locals {
179+
# Always inject Coder workspace identifiers so OTEL data can be joined with
180+
# Coder's audit log / exectrace on workspace_id without per-template wiring.
181+
otel_resource_attributes = merge(
182+
var.telemetry.resource_attributes,
183+
{
184+
"coder.workspace_id" = data.coder_workspace.me.id
185+
"coder.workspace_name" = data.coder_workspace.me.name
186+
"coder.workspace_owner" = data.coder_workspace_owner.me.name
187+
"coder.workspace_owner_id" = data.coder_workspace_owner.me.id
188+
"coder.template_name" = data.coder_workspace.me.template_name
189+
"coder.template_version" = data.coder_workspace.me.template_version
190+
"coder.access_url" = data.coder_workspace.me.access_url
191+
},
192+
)
193+
}
194+
195+
resource "coder_env" "claude_code_enable_telemetry" {
196+
count = var.telemetry.enabled ? 1 : 0
197+
agent_id = var.agent_id
198+
name = "CLAUDE_CODE_ENABLE_TELEMETRY"
199+
value = "1"
200+
}
201+
202+
resource "coder_env" "otel_exporter_otlp_endpoint" {
203+
count = var.telemetry.enabled && var.telemetry.otlp_endpoint != "" ? 1 : 0
204+
agent_id = var.agent_id
205+
name = "OTEL_EXPORTER_OTLP_ENDPOINT"
206+
value = var.telemetry.otlp_endpoint
207+
}
208+
209+
resource "coder_env" "otel_exporter_otlp_protocol" {
210+
count = var.telemetry.enabled ? 1 : 0
211+
agent_id = var.agent_id
212+
name = "OTEL_EXPORTER_OTLP_PROTOCOL"
213+
value = var.telemetry.otlp_protocol
214+
}
215+
216+
resource "coder_env" "otel_exporter_otlp_headers" {
217+
count = var.telemetry.enabled && length(var.telemetry.otlp_headers) > 0 ? 1 : 0
218+
agent_id = var.agent_id
219+
name = "OTEL_EXPORTER_OTLP_HEADERS"
220+
value = join(",", [for k, v in var.telemetry.otlp_headers : "${k}=${v}"])
221+
}
222+
223+
resource "coder_env" "otel_resource_attributes" {
224+
count = var.telemetry.enabled ? 1 : 0
225+
agent_id = var.agent_id
226+
name = "OTEL_RESOURCE_ATTRIBUTES"
227+
value = join(",", [for k, v in local.otel_resource_attributes : "${k}=${v}"])
228+
}
229+
166230
locals {
167231
workdir = var.workdir != null ? trimsuffix(var.workdir, "/") : ""
168232
install_script = templatefile("${path.module}/scripts/install.sh.tftpl", {

0 commit comments

Comments
 (0)