Morgan Lunt 4d96be0de7
feat(claude-code): add telemetry input for OTEL export with workspace attribution (#862)
## Problem

Claude Code ships an OpenTelemetry exporter for token usage, tool calls,
session lifecycle and errors
(https://docs.anthropic.com/en/docs/claude-code/monitoring-usage), but
the module exposes no first-class wiring for it. Template authors who
want telemetry have to know the env var names
(`CLAUDE_CODE_ENABLE_TELEMETRY`, the `OTEL_EXPORTER_OTLP_*` family) and
write their own `coder_env` blocks. More importantly there is no
convention for how to correlate Claude Code telemetry with Coder's own
audit logs and `exectrace` records, so even when both are exported they
end up as two unjoined datasets.

## Change

Adds a `telemetry` input that turns on `CLAUDE_CODE_ENABLE_TELEMETRY`
and the standard OTLP exporter env vars in one place:

```tf
telemetry = {
  enabled       = true
  otlp_endpoint = "http://otel-collector.observability:4317"
  otlp_protocol = "grpc"
  otlp_headers  = { authorization = "Bearer ..." }
  resource_attributes = { "service.name" = "claude-code" }
}
```

When enabled, the module automatically appends `coder.workspace_id`,
`coder.workspace_name`, `coder.workspace_owner` and
`coder.template_name` to `OTEL_RESOURCE_ATTRIBUTES`. This gives a stable
join key between Claude Code spans/metrics and Coder's audit log and
exectrace events on `workspace_id`, so a platform team can answer "show
me every shell command Claude executed in workspace X alongside the
token spend for that session" without custom plumbing.

This is purely additive (`coder_env` resources behind `count`), defaults
to disabled, and is independent of how Claude is launched, so it
composes cleanly with the install-only direction in #861.

## Validation

- `terraform fmt`, `terraform validate`, `terraform test` (19/19) pass
- `bun test -t telemetry` (2/2) pass: env vars are set with the expected
values when enabled, and absent when the input is omitted

Disclosure: I work at Anthropic on the Claude Code team.

---------

Co-authored-by: DevCats <chris@dualriver.com>
Co-authored-by: Atif Ali <me@matifali.dev>
2026-04-29 13:07:30 -05:00

13 KiB

display_name description icon verified tags
Claude Code Install and configure the Claude Code CLI in your workspace. ../../../../.icons/claude.svg true
agent
claude-code
ai
anthropic
ai-gateway

Claude Code

Install and configure the Claude Code CLI in your workspace. Starting Claude is left to the caller (template command, IDE launcher, or a custom coder_script).

module "claude-code" {
  source            = "registry.coder.com/coder/claude-code/coder"
  version           = "5.1.0"
  agent_id          = coder_agent.main.id
  anthropic_api_key = "xxxx-xxxxx-xxxx"
}

Warning

If upgrading from v4.x.x of this module: v5 is a major refactor that drops support for Coder Tasks and Boundary. We plan to add those back in a follow-up. Keep using v4.x.x if you depend on them. See #861 for the full migration guide.

Prerequisites

Provide exactly one authentication method:

  • Anthropic API key: get one from the Anthropic Console and pass it as anthropic_api_key.
  • Claude.ai OAuth token (Pro, Max, or Enterprise accounts): generate one by running claude setup-token locally and pass it as claude_code_oauth_token.
  • Coder AI Gateway (Coder Premium, Coder >= 2.30.0): set enable_ai_gateway = true. The module authenticates against the gateway using the workspace owner's session token. Do not combine with anthropic_api_key or claude_code_oauth_token.

workdir

workdir is optional. When set, the module pre-creates the directory if it is missing and pre-accepts the Claude Code trust/onboarding prompt for it in ~/.claude.json. Leave workdir unset if you only want the module to install the CLI and configure authentication; users can still open any project interactively and accept the trust dialog per project.

Examples

Standalone mode with a launcher app

Authenticate Claude directly against Anthropic's API and add a coder_app that users can click from the workspace dashboard to open an interactive Claude session.

locals {
  claude_workdir = "/home/coder/project"
}

module "claude-code" {
  source            = "registry.coder.com/coder/claude-code/coder"
  version           = "5.1.0"
  agent_id          = coder_agent.main.id
  workdir           = local.claude_workdir
  anthropic_api_key = "xxxx-xxxxx-xxxx"
}

resource "coder_app" "claude" {
  agent_id     = coder_agent.main.id
  slug         = "claude"
  display_name = "Claude Code"
  icon         = "/icon/claude.svg"
  open_in      = "slim-window"
  command      = <<-EOT
    #!/bin/bash
    set -e
    cd ${local.claude_workdir}
    claude
  EOT
}

Note

coder_app.command runs when the user clicks the app tile. Combine with anthropic_api_key, claude_code_oauth_token, or enable_ai_gateway = true on the module to pre-authenticate the CLI.

Usage with AI Gateway

AI Gateway is a Premium Coder feature that provides centralized LLM proxy management. Requires Coder >= 2.30.0.

module "claude-code" {
  source            = "registry.coder.com/coder/claude-code/coder"
  version           = "5.1.0"
  agent_id          = coder_agent.main.id
  workdir           = "/home/coder/project"
  enable_ai_gateway = true
}

When enable_ai_gateway = true, the module sets:

  • ANTHROPIC_BASE_URL to ${data.coder_workspace.me.access_url}/api/v2/aibridge/anthropic
  • ANTHROPIC_AUTH_TOKEN to the workspace owner's Coder session token

Claude Code then routes API requests through Coder's AI Gateway instead of directly to Anthropic.

Caution

enable_ai_gateway = true is mutually exclusive with anthropic_api_key and claude_code_oauth_token. Setting any of them together fails at plan time.

Advanced Configuration

This example shows version pinning, a pre-installed binary path, a custom model, and MCP servers.

module "claude-code" {
  source   = "registry.coder.com/coder/claude-code/coder"
  version  = "5.1.0"
  agent_id = coder_agent.main.id
  workdir  = "/home/coder/project"

  anthropic_api_key = "xxxx-xxxxx-xxxx"

  claude_code_version = "2.0.62" # Pin to a specific Claude CLI version.

  # Skip the module's installer and point at a pre-installed Claude binary.
  # claude_binary_path can only be customized when install_claude_code is false.
  install_claude_code = false
  claude_binary_path  = "/opt/claude/bin"

  model = "sonnet"

  mcp = <<-EOF
  {
    "mcpServers": {
      "my-custom-tool": {
        "command": "my-tool-server",
        "args": ["--port", "8080"]
      }
    }
  }
  EOF

  mcp_config_remote_path = [
    "https://gist.githubusercontent.com/35C4n0r/cd8dce70360e5d22a070ae21893caed4/raw/",
    "https://raw.githubusercontent.com/coder/coder/main/.mcp.json"
  ]
}

Note

Swap anthropic_api_key for claude_code_oauth_token = "xxxxx-xxxx-xxxx" to authenticate via a Claude.ai OAuth token instead. Pass exactly one.

Note

Servers configured through mcp or mcp_config_remote_path are added at Claude Code's user scope, making them available across every project the workspace owner opens. For project-local MCP servers, commit a .mcp.json to the project repository instead.

Note

Remote URLs should return a JSON body in the following format:

{
  "mcpServers": {
    "server-name": {
      "command": "some-command",
      "args": ["arg1", "arg2"]
    }
  }
}

The Content-Type header doesn't matter, both text/plain and application/json work fine.

Serialize a downstream coder_script after the install pipeline

The module exposes the coder exp sync name of each script it creates via the scripts output: an ordered list (pre_install, install, post_install) of names for scripts this module actually creates. Scripts that were not configured are absent from the list.

Downstream coder_script resources can wait for this module's install pipeline to finish using coder exp sync want <self> <each name>:

module "claude-code" {
  source            = "registry.coder.com/coder/claude-code/coder"
  version           = "5.1.0"
  agent_id          = coder_agent.main.id
  workdir           = "/home/coder/project"
  anthropic_api_key = "xxxx-xxxxx-xxxx"
}

resource "coder_script" "post_claude" {
  agent_id     = coder_agent.main.id
  display_name = "Run after Claude Code install"
  run_on_start = true
  script       = <<-EOT
    #!/bin/bash
    set -euo pipefail
    trap 'coder exp sync complete post-claude' EXIT
    coder exp sync want post-claude ${join(" ", module.claude-code.scripts)}
    coder exp sync start post-claude

    # Your work here runs after claude-code finishes installing.
    claude --version
  EOT
}

Usage with AWS Bedrock

Prerequisites

AWS account with Bedrock access, Claude models enabled in Bedrock console, and appropriate IAM permissions.

Configure Claude Code to use AWS Bedrock for accessing Claude models through your AWS infrastructure.

resource "coder_env" "bedrock_use" {
  agent_id = coder_agent.main.id
  name     = "CLAUDE_CODE_USE_BEDROCK"
  value    = "1"
}

resource "coder_env" "aws_region" {
  agent_id = coder_agent.main.id
  name     = "AWS_REGION"
  value    = "us-east-1" # Choose your preferred region
}

# Option 1: Using AWS credentials

variable "aws_access_key_id" {
  type        = string
  description = "Your AWS access key ID. Create this in the AWS IAM console under 'Security credentials'."
  sensitive   = true
}

variable "aws_secret_access_key" {
  type        = string
  description = "Your AWS secret access key. This is shown once when you create an access key in the AWS IAM console."
  sensitive   = true
}

resource "coder_env" "aws_access_key_id" {
  agent_id = coder_agent.main.id
  name     = "AWS_ACCESS_KEY_ID"
  value    = var.aws_access_key_id
}

resource "coder_env" "aws_secret_access_key" {
  agent_id = coder_agent.main.id
  name     = "AWS_SECRET_ACCESS_KEY"
  value    = var.aws_secret_access_key
}

# Option 2: Using Bedrock API key (simpler)

variable "aws_bearer_token_bedrock" {
  type        = string
  description = "Your AWS Bedrock bearer token. This provides access to Bedrock without needing separate access key and secret key."
  sensitive   = true
}

resource "coder_env" "bedrock_api_key" {
  agent_id = coder_agent.main.id
  name     = "AWS_BEARER_TOKEN_BEDROCK"
  value    = var.aws_bearer_token_bedrock
}

module "claude-code" {
  source   = "registry.coder.com/coder/claude-code/coder"
  version  = "5.1.0"
  agent_id = coder_agent.main.id
  workdir  = "/home/coder/project"
  model    = "global.anthropic.claude-sonnet-4-5-20250929-v1:0"
}

Note

For additional Bedrock configuration options (model selection, token limits, region overrides, etc.), see the Claude Code Bedrock documentation.

Usage with Google Vertex AI

Prerequisites

GCP project with Vertex AI API enabled, Claude models enabled through Model Garden, service account with Vertex AI permissions, and appropriate IAM permissions (Vertex AI User role).

Configure Claude Code to use Google Vertex AI for accessing Claude models through Google Cloud Platform.

variable "vertex_sa_json" {
  type        = string
  description = "The complete JSON content of your Google Cloud service account key file. Create a service account in the GCP Console under 'IAM & Admin > Service Accounts', then create and download a JSON key. Copy the entire JSON content into this variable."
  sensitive   = true
}

resource "coder_env" "vertex_use" {
  agent_id = coder_agent.main.id
  name     = "CLAUDE_CODE_USE_VERTEX"
  value    = "1"
}

resource "coder_env" "vertex_project_id" {
  agent_id = coder_agent.main.id
  name     = "ANTHROPIC_VERTEX_PROJECT_ID"
  value    = "your-gcp-project-id"
}

resource "coder_env" "cloud_ml_region" {
  agent_id = coder_agent.main.id
  name     = "CLOUD_ML_REGION"
  value    = "global"
}

resource "coder_env" "vertex_sa_json" {
  agent_id = coder_agent.main.id
  name     = "VERTEX_SA_JSON"
  value    = var.vertex_sa_json
}

resource "coder_env" "google_application_credentials" {
  agent_id = coder_agent.main.id
  name     = "GOOGLE_APPLICATION_CREDENTIALS"
  value    = "/tmp/gcp-sa.json"
}

module "claude-code" {
  source   = "registry.coder.com/coder/claude-code/coder"
  version  = "5.1.0"
  agent_id = coder_agent.main.id
  workdir  = "/home/coder/project"
  model    = "claude-sonnet-4@20250514"

  pre_install_script = <<-EOT
    #!/bin/bash
    # Write the service account JSON to a file
    echo "$VERTEX_SA_JSON" > /tmp/gcp-sa.json

    # Install prerequisite packages
    sudo apt-get update
    sudo apt-get install -y apt-transport-https ca-certificates gnupg curl

    # Add Google Cloud public key
    curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo gpg --dearmor -o /usr/share/keyrings/cloud.google.gpg

    # Add Google Cloud SDK repo to apt sources
    echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" | sudo tee /etc/apt/sources.list.d/google-cloud-sdk.list

    # Update and install the Google Cloud SDK
    sudo apt-get update && sudo apt-get install -y google-cloud-cli

    # Authenticate gcloud with the service account
    gcloud auth activate-service-account --key-file=/tmp/gcp-sa.json
  EOT
}

Note

For additional Vertex AI configuration options (model selection, token limits, region overrides, etc.), see the Claude Code Vertex AI documentation.

Telemetry export (OpenTelemetry)

Claude Code can emit OpenTelemetry metrics and events covering token usage, tool calls, session lifecycle, and errors (see the monitoring docs). Set telemetry.enabled = true and point otlp_endpoint at your OTLP collector.

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 and exectrace records on workspace_id.

module "claude-code" {
  source            = "registry.coder.com/coder/claude-code/coder"
  version           = "5.1.0"
  agent_id          = coder_agent.main.id
  workdir           = "/home/coder/project"
  anthropic_api_key = "xxxx-xxxxx-xxxx"

  telemetry = {
    enabled       = true
    otlp_endpoint = "http://otel-collector.observability:4317"
    otlp_protocol = "grpc"
    otlp_headers = {
      authorization = "Bearer ${var.otel_token}"
    }
    resource_attributes = {
      "service.name" = "claude-code"
    }
  }
}

Troubleshooting

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

# Installation logs
cat ~/.coder-modules/coder/claude-code/logs/install.log

# Pre/post install script logs
cat ~/.coder-modules/coder/claude-code/logs/pre_install.log
cat ~/.coder-modules/coder/claude-code/logs/post_install.log

References