Claude Code metrics: the OpenTelemetry reference

Claude Code ships a full OpenTelemetry exporter that almost nobody turns on. If you already run Prometheus, Datadog, Grafana Cloud, or Honeycomb, it is roughly ten minutes to having agent usage in the same dashboards as everything else you operate, and it is the only per-user telemetry that works on every deployment including Bedrock and Vertex.

By the Continuum team. We build a workbench that runs Claude Code, Codex, and their peers, so the model rates quoted here are the ones our own cost analytics ship with.

The short version

Set CLAUDE_CODE_ENABLE_TELEMETRY=1 plus the standard OTLP variables and Claude Code exports eight counters: sessions, tokens, estimated cost, lines of code, commits, pull requests, code-edit decisions, and active time. Metrics, logs, and traces are three separate exporters. Two defaults matter: session.id and user.account_uuid are attached to metrics unless you turn them off, and the temporality preference is delta, which Prometheus will not accept without conversion. Content logging is off by default.

What you need to know
  • One variable enables it: CLAUDE_CODE_ENABLE_TELEMETRY=1.
  • Metrics, logs, and traces are three separate exporters, configured independently.
  • session.id ships in metrics by default. Set OTEL_METRICS_INCLUDE_SESSION_ID=false.
  • Temporality defaults to delta. Prometheus needs cumulative.
  • Prompt and response content is off by default and stays off unless you opt in.

Turning it on

A working configuration against a collector on the default OTLP gRPC port.
export CLAUDE_CODE_ENABLE_TELEMETRY=1

export OTEL_METRICS_EXPORTER=otlp
export OTEL_LOGS_EXPORTER=otlp
export OTEL_EXPORTER_OTLP_PROTOCOL=grpc
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317

# cardinality: opt out of the two attributes that are on by default
export OTEL_METRICS_INCLUDE_SESSION_ID=false
export OTEL_METRICS_INCLUDE_ACCOUNT_UUID=false

# shorter intervals while you are verifying it works
export OTEL_METRIC_EXPORT_INTERVAL=10000

The variables worth knowing, with the defaults as of August 2026.

VariableDefaultPurpose
CLAUDE_CODE_ENABLE_TELEMETRYoffMaster switch. Nothing is exported without it
OTEL_METRICS_EXPORTERnoneotlp, prometheus, console, or none
OTEL_LOGS_EXPORTERnoneEvents. Separate from metrics
OTEL_TRACES_EXPORTERnoneSpans. Needs the beta flag as well
OTEL_EXPORTER_OTLP_PROTOCOLnonegrpc, http/protobuf, or http/json
OTEL_EXPORTER_OTLP_ENDPOINTnoneYour collector. Per-signal overrides exist
OTEL_EXPORTER_OTLP_HEADERSnoneAuth for a hosted backend
OTEL_METRIC_EXPORT_INTERVAL60000Milliseconds
OTEL_LOGS_EXPORT_INTERVAL5000Milliseconds
OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCEdeltadelta or cumulative
OTEL_RESOURCE_ATTRIBUTESnoneYour own labels, for example team=payments
One master switch enables three independent OpenTelemetry exporters, metrics, events, and traces, each with its own variable and interval, converging on one collectorclaudethe CLIMASTER SWITCHCLAUDE_CODE_ENABLE_TELEMETRY=1METRICSOTEL_METRICS_EXPORTER8 counters · 60s · deltaEVENTSOTEL_LOGS_EXPORTER15 events · 5s · content offTRACESOTEL_TRACES_EXPORTERspans · beta flag as wellyour collectorPrometheus · DatadogGrafana · Honeycomb×metrics carry session.id by default: one new series per session×metrics export delta; Prometheus needs cumulative or rate() reads zero

The eight metrics

Every exported metric. All are counters.

MetricUnitCounts
claude_code.session.countnoneCLI sessions started
claude_code.token.usagetokensTokens, split by the type attribute
claude_code.cost.usageUSDEstimated cost of the session
claude_code.lines_of_code.countnoneLines added or removed
claude_code.commit.countnoneCommits created through Claude Code
claude_code.pull_request.countnonePull requests created through Claude Code
claude_code.code_edit_tool.decisionnoneEdit permission decisions, accept or reject
claude_code.active_time.totalsActive engagement time

The attributes that turn a total into an answer

The attribute set on the token and cost counters is far richer than most people realise, and it is where the interesting queries come from. It answers which subsystem spent the money, not just which model.

Attributes on the token and cost counters.

AttributeValuesWhy you want it
modelclaude-sonnet-5 and similarThe single biggest cost lever
typeinput, output, cacheRead, cacheCreationGives you the cache hit ratio
query_sourcemain, subagent, auxiliarySeparates the agent from its helpers
effortlow to maxThinking budget, billed as output
speedfast, normalFast mode is priced separately
agent.nameSubagent typeWhich subagent is expensive
skill.nameActive skillWhich skill is expensive
mcp_server.nameMCP serverWhich integration is expensive
plugin.nameOwning pluginThird-party names redacted by default

Global attributes, and which ones are on metrics by default.

AttributeOn metrics by defaultCardinality
session.idYesUnbounded. This is the hazard
user.account_uuidYesBounded by headcount
organization.idYesOne value
app.versionNoLow, and useful for regressions
app.entrypointNoLow: cli, sdk-cli, claude-vscode
terminal.typeYes, on eventsLow
Belt and braces: strip it at the collector too, and fix temporality for Prometheus.
processors:
  attributes/strip:
    actions:
      - key: session.id
        action: delete
  deltatocumulative: {}

service:
  pipelines:
    metrics:
      receivers: [otlp]
      processors: [attributes/strip, deltatocumulative, batch]
      exporters: [prometheus]

Events and traces

The logs exporter carries fifteen structured events. They are the layer where per-request detail lives, and they are what you want when a metric spike needs explaining.

  • claude_code.user_prompt, claude_code.assistant_response
  • claude_code.api_request, claude_code.api_error, claude_code.api_refusal
  • claude_code.api_request_body, claude_code.api_response_body
  • claude_code.tool_result, claude_code.tool_decision
  • claude_code.permission_mode_changed, claude_code.auth
  • claude_code.mcp_server_connection, claude_code.internal_error
  • claude_code.plugin_installed, claude_code.plugin_loaded

The most useful of these is claude_code.api_request, which carries cost_usd, cost_usd_micros, duration_ms, ttft_ms on the span side, all four token counts, the request id, and the same attribution attributes as the counters. It is a per-request billing record.

Span tracing, in beta

Setting CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1 alongside a traces exporter produces spans: claude_code.interaction wrapping a whole turn, claude_code.llm_request per model call with ttft_ms and retry attempt, claude_code.tool per tool call, and claude_code.tool.blocked_on_user measuring how long the agent waited on a permission prompt.

That last span is the one to instrument first if you are trying to work out where agent time actually goes. It separates "the model is slow" from "the human did not answer the prompt for four minutes", and those have completely different fixes.

Queries worth having

PromQL. Check your own /metrics for the exact series names first.
# spend per day
sum(increase(claude_code_cost_usage_total[1d]))

# spend by model
sum by (model) (increase(claude_code_cost_usage_total[1d]))

# spend by subsystem: main thread vs subagents vs auxiliary
sum by (query_source) (increase(claude_code_cost_usage_total[1d]))

# cache hit ratio: the health metric for agent efficiency
sum(rate(claude_code_token_usage_total{type="cacheRead"}[1h]))
  /
sum(rate(claude_code_token_usage_total{type=~"input|cacheRead|cacheCreation"}[1h]))

# how often people reject the agent edits
sum(rate(claude_code_code_edit_tool_decision_total{decision="reject"}[1d]))
  /
sum(rate(claude_code_code_edit_tool_decision_total[1d]))

# adoption: distinct engineers active this week
count(count by (user_account_uuid) (
  increase(claude_code_session_count_total[7d]) > 0
))

The rejection rate is the most interesting series in the set and the least used. A rising rejection rate is an early signal that a model change, a prompt convention, or a project memory file has made the agent worse, and it moves before anyone files a complaint.

Rolling it out to a team

Asking every engineer to export eight variables produces partial coverage and silent gaps, and the gaps are invisible: a machine with telemetry off looks exactly like a machine that had a quiet week. Ship the configuration with the tool instead.

A managed settings file, deployed by whatever manages your fleet.
{
  "env": {
    "CLAUDE_CODE_ENABLE_TELEMETRY": "1",
    "OTEL_METRICS_EXPORTER": "otlp",
    "OTEL_LOGS_EXPORTER": "otlp",
    "OTEL_EXPORTER_OTLP_PROTOCOL": "http/protobuf",
    "OTEL_EXPORTER_OTLP_ENDPOINT": "https://otel.internal.example.com",
    "OTEL_EXPORTER_OTLP_HEADERS": "Authorization=Bearer <token>",
    "OTEL_METRICS_INCLUDE_SESSION_ID": "false",
    "OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE": "cumulative",
    "OTEL_RESOURCE_ATTRIBUTES": "team=payments,env=prod"
  }
}

For a backend that needs short-lived credentials, set otelHeadersHelper in settings to a script that prints a JSON object of headers. Claude Code re-runs it on an interval, so a token with a one-hour life works without anyone rotating anything by hand.

Questions people ask

Set CLAUDE_CODE_ENABLE_TELEMETRY=1 along with OTEL_METRICS_EXPORTER, OTEL_EXPORTER_OTLP_PROTOCOL, and OTEL_EXPORTER_OTLP_ENDPOINT. Metrics, logs, and traces are three separate exporters and each needs its own variable.

Eight counters as of August 2026: session.count, token.usage, cost.usage, lines_of_code.count, commit.count, pull_request.count, code_edit_tool.decision, and active_time.total, with attributes for model, token type, query source, effort, speed, subagent, skill, plugin, and MCP server.

No. It is computed locally from token counts at standard list rates, so it ignores promotional pricing and contracted discounts. On a subscription no money moved at all. Treat it as intensity, and use the Console usage page for billing.

Almost certainly session.id, which is attached to metrics by default and is unbounded. Set OTEL_METRICS_INCLUDE_SESSION_ID=false, and consider OTEL_METRICS_INCLUDE_ACCOUNT_UUID=false if you do not need per-person breakdowns.

Temporality. Claude Code exports delta by default and Prometheus is cumulative. Set OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE=cumulative, or run the deltatocumulative processor in your collector.

Yes. Both accept OTLP. Set the endpoint and put your key in OTEL_EXPORTER_OTLP_HEADERS, or route through a collector. For rotating credentials, point otelHeadersHelper at a script that prints headers as JSON.

Not by default. Prompt text, assistant responses, tool parameters, and raw API bodies are all redacted unless you opt in with OTEL_LOG_USER_PROMPTS, OTEL_LOG_ASSISTANT_RESPONSES, OTEL_LOG_TOOL_DETAILS, or OTEL_LOG_RAW_API_BODIES. Whatever you enable goes only to the endpoint you configured.

Yes, and it is the main reason to use it there. The Claude Code Analytics API and the first-party dashboards do not cover cloud-provider deployments, so OpenTelemetry export from each machine is the supported route to per-user attribution.

Sources

Every figure above was read from these pages on August 2026. Vendors reprice without notice; if you find a stale number, tell us.

  1. Claude Code: monitoring with OpenTelemetry
  2. Claude Code: manage costs effectively
  3. Claude Code settings reference
  4. OpenTelemetry documentation
Try it

Metrics without
a collector.

Continuum reads what Claude Code and Codex already write and shows spend by repo, model, and day, plus the live quota OpenTelemetry cannot export.

free app · your subscriptions · local-first