Back to blog
October 27, 20267 min readEverstack Team

How to Trace Claude Code, Codex, Gemini CLI and Cursor with OpenTelemetry

Coding agents already speak OpenTelemetry. The exact config to send Claude Code, Codex, Gemini CLI, GitHub Copilot and Cursor sessions to one trace view, with token usage and cost, and what each agent does not emit.

Coding AgentsOpenTelemetryObservabilityClaude Code

A team running three coding agents has three sets of logs and no way to answer the questions that matter: which sessions cost the most, which tool calls failed, and which model was behind a bad change. Most of those agents already export OpenTelemetry. The work is pointing them at one place and normalising what arrives.

This guide gives the configuration for each agent, sending to an Everstack instance. The OTLP settings are standard, so they also show what to set for any OTLP backend.

What every agent needs

An OTLP/HTTP endpoint and an API key sent as a Bearer token. Everstack accepts the three signals at:

| Signal | Endpoint | | --- | --- | | Traces | POST https://<your-instance>/v1/traces | | Logs | POST https://<your-instance>/v1/logs | | Metrics | POST https://<your-instance>/v1/metrics |

Most exporters take a base endpoint and append the signal path themselves:

export OTEL_EXPORTER_OTLP_ENDPOINT=https://<your-instance>
export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer <your-api-key>"

One thing catches everyone: telemetry initialises when the agent process starts. Only sessions started after you add the config will emit, so restart the agent rather than sending another prompt.

Claude Code

Claude Code has native OpenTelemetry. Metrics and events work out of the box; the full trace tree, from interaction to LLM request, tool call and sub-agent, is behind a beta flag.

export CLAUDE_CODE_ENABLE_TELEMETRY=1
export CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1   # trace span tree (beta)
export OTEL_TRACES_EXPORTER=otlp
export OTEL_METRICS_EXPORTER=otlp
export OTEL_LOGS_EXPORTER=otlp
export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
export OTEL_EXPORTER_OTLP_ENDPOINT=https://<your-instance>
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer <your-api-key>"
export OTEL_LOG_TOOL_DETAILS=1                  # tool names and arguments

To apply this to every session, put the same variables in the env block of ~/.claude/settings.json. To keep the key out of that file, Claude Code's otelHeadersHelper setting can point at a script that prints the header as JSON.

Claude Code is the one common coding agent that reports a dollar cost directly. Everstack uses that figure as-is and never recomputes it.

Codex

OpenAI's Codex CLI exports OpenTelemetry from the [otel] table in ~/.codex/config.toml:

[otel]
exporter = "otlp-http"
endpoint = "https://<your-instance>/v1/traces"
headers = { Authorization = "Bearer <your-api-key>" }

Coverage is uneven across subcommands: full in the interactive TUI, none in codex mcp-server. Codex emits token counts but no cost, and groups a session under a conversation.id.

Gemini CLI

Gemini CLI has built-in OpenTelemetry, but traces are off by default and it ignores the standard OTEL_EXPORTER_OTLP_ENDPOINT. Configure it in ~/.gemini/settings.json:

{
  "telemetry": {
    "enabled": true,
    "target": "otlp",
    "otlpEndpoint": "https://<your-instance>",
    "otlpHeaders": { "Authorization": "Bearer <your-api-key>" }
  }
}

Setting names vary between Gemini CLI releases, so check its telemetry docs for your version. Like Codex, it reports tokens but no cost.

GitHub Copilot

Copilot Chat in VS Code exports OpenTelemetry natively:

{
  "github.copilot.chat.otel.enabled": true,
  "github.copilot.chat.otel.exporterType": "otlp-http",
  "github.copilot.chat.otel.otlpEndpoint": "https://<your-instance>"
}

The endpoint setting cannot carry an auth header, so pass the key through OTEL_EXPORTER_OTLP_HEADERS in the shell VS Code launches from.

Cursor

Cursor does not export OpenTelemetry for its model calls. The way to see its sessions is to route its traffic through an OpenAI-compatible gateway, which records each request itself. In Settings → Models → API Keys, set the OpenAI key to a gateway key, enable Override OpenAI Base URL, and point it at https://<your-instance>/v1.

The limit is Cursor's, not the gateway's: Cursor honours a custom base URL only for its Ask and Plan chat modes. Agent, Composer inline edit and Tab autocomplete run against Cursor's own backend and will not appear.

What you can see once it is flowing

Each agent names the same things differently. Token counts arrive as input_token_count on one record and gen_ai.usage.input_tokens on another; one agent reports cost and the rest do not. A useful backend normalises all of it, so in Everstack every session renders the same way:

  • Model and provider, inferred from the model name when the agent does not report it.
  • Token usage, input, output and cached, coalesced across attribute names.
  • Cost, as reported where the agent reports it, otherwise computed from the same model-catalog pricing used for billing, so a trace's cost matches the bill.
  • Tool calls and sub-agents, nested in the trace tree.

That turns "which agent is expensive" from a guess into a filter. Group by service name to compare agents, by session.id to replay one run, and by model to see what a switch would save. For per-model prices to plan with, the model catalog lists every provider's published rates.

Where to go next