Quick start
Configure OpenTelemetry using environment variables:claude_code.session.count metric, which Claude Code emits when a session starts. To verify a logs-only setup, submit a prompt and check for the claude_code.user_prompt event. If nothing arrives, run claude --debug and check the debug log for OTel export errors.
For full configuration options, see the OpenTelemetry specification.
Administrator configuration
Administrators can configure OpenTelemetry settings for all users through the managed settings file. See the settings precedence for more information about how settings are applied. Example managed settings configuration:OTEL_* environment variables to the subprocesses it spawns, including the Bash tool, hooks, MCP servers, and language servers. An OpenTelemetry-instrumented application that you run through the Bash tool doesn’t inherit Claude Code’s exporter endpoint or headers, so set those variables directly in the command if that application needs to export its own telemetry.
How managed settings lock the OTLP destination
When you set anOTEL_EXPORTER_OTLP_* variable in managed settings, Claude Code removes conflicting developer-set variables at startup and logs a warning you can see with claude --debug. What it removes depends on which variable you set:
- Endpoints: when you set
OTEL_EXPORTER_OTLP_ENDPOINT, Claude Code removes every developer-set per-signal endpoint. Developers can’t point one signal at a different collector, so you don’t need to also set the per-signal endpoint variables in managed settings. - Protocols: when you set
OTEL_EXPORTER_OTLP_PROTOCOL, Claude Code removes every developer-set per-signal protocol. - Credentials: when you set
OTEL_EXPORTER_OTLP_HEADERS,OTEL_EXPORTER_OTLP_CLIENT_KEY, orOTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE, Claude Code removes the developer-set per-signal versions of that variable, plus every developer-set endpoint variable, generic or per-signal, since those credentials would otherwise reach a collector the managed settings didn’t choose. - Exporter selectors:
OTEL_METRICS_EXPORTER,OTEL_LOGS_EXPORTER, and the betaOTEL_TRACES_EXPORTERfollow normal per-key precedence. A developer’s setting can still disable a signal or switch it to the console exporter, so set the selectors in managed settings too if you need them locked. Across admin sources,OTEL_LOGS_EXPORTERfollows the telemetry unit while the other two selectors merge per key. Requires Claude Code v2.1.223 or later.
Configuration details
Common configuration variables
These variables configure exporters, endpoints, and export behavior for all deployments. If you set a per-signal endpoint or protocol variable, such asOTEL_EXPORTER_OTLP_METRICS_ENDPOINT, Claude Code uses it instead of the generic variable for that signal. If you set a per-signal headers variable, such as OTEL_EXPORTER_OTLP_METRICS_HEADERS, Claude Code merges it with the generic OTEL_EXPORTER_OTLP_HEADERS for that signal. On machines with managed settings, see How managed settings lock the OTLP destination for what Claude Code removes.
For the
http/protobuf and http/json protocols, Claude Code sends each export request with a Content-Length header. Before v2.1.212, Claude Code versions from v2.1.191 onward sent these requests with chunked transfer encoding; Azure Monitor and other endpoints that require a declared length rejected them with 411 Length Required or 400 errors.
mTLS authentication
How you configure client certificates for the OTLP exporter depends on the OTLP protocol in use for that signal, set viaOTEL_EXPORTER_OTLP_PROTOCOL or the per-signal override. The same configuration applies to metrics, logs, and traces.
For
grpc, the OpenTelemetry SDK reads the standard OTLP variables directly, so existing configurations that set the per-signal metrics variables continue to work. On machines with managed settings, Claude Code may remove developer-set per-signal credentials and endpoints at startup.
Metrics cardinality control
The following environment variables control which attributes are included in metrics to manage cardinality:
Lower cardinality generally means better performance and lower storage costs but less granular data for analysis.
Traces (beta)
Distributed tracing exports spans that link each user prompt to the API requests and tool executions it triggers, so you can view a full request as a single trace in your tracing backend. Tracing is off by default. To enable it, set bothCLAUDE_CODE_ENABLE_TELEMETRY=1 and CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1, then set OTEL_TRACES_EXPORTER to choose where spans are sent. Traces reuse the common OTLP configuration for endpoint, protocol, headers, and mTLS. On machines with managed settings, Claude Code may remove developer-set per-signal credentials and endpoints at startup.
Spans redact user prompt text, tool input details, and tool content by default. Set
OTEL_LOG_USER_PROMPTS=1, OTEL_LOG_TOOL_DETAILS=1, and OTEL_LOG_TOOL_CONTENT=1 to include them.
When tracing is active, Bash and PowerShell subprocesses automatically inherit a TRACEPARENT environment variable containing the W3C trace context of the active tool execution span. This lets any subprocess that reads TRACEPARENT parent its own spans under the same trace, enabling end-to-end distributed tracing through scripts and commands that Claude runs.
When tracing is active and Claude Code is connected directly to the Anthropic API, each model request carries a W3C traceparent header set to the claude_code.llm_request span’s context, and the API’s traceresponse header is recorded as a span link. Together these connect Claude Code’s client-side spans to the server-side trace through any compliant intermediary. Outbound HTTP MCP requests carry traceparent the same way. The header is not sent to third-party providers.
By default, the traceparent header on model and HTTP MCP requests is sent only when ANTHROPIC_BASE_URL is unset or points at the Anthropic API, since some proxies reject unrecognized headers. The subprocess TRACEPARENT variable is controlled by the same switch for consistency. If you run Claude Code through a custom ANTHROPIC_BASE_URL proxy and want trace context propagated, set CLAUDE_CODE_PROPAGATE_TRACEPARENT=1.
In Agent SDK and non-interactive sessions started with -p, Claude Code also reads TRACEPARENT and TRACESTATE from its own environment when starting each interaction span. This lets an embedding process pass its active W3C trace context into the subprocess so Claude Code’s spans appear as children of the caller’s distributed trace. Interactive sessions ignore inbound TRACEPARENT to avoid accidentally inheriting ambient values from CI or container environments.
The inbound trace context also applies to events. In Agent SDK and -p sessions with TRACEPARENT set, each OTLP event log record carries trace_id and span_id values that join it to your application’s trace, even when the traces exporter isn’t configured, so your logging backend can correlate events with the rest of the trace.
A record emitted while an interaction is active carries the interaction span’s IDs, even when Claude Code emits it outside the span’s async context, such as in a permission prompt callback or for a record buffered during startup and exported later. A record emitted with no active interaction span carries the inbound TRACEPARENT IDs directly. Before v2.1.214, records emitted outside the span’s async context carried the inbound TRACEPARENT IDs instead of the span’s IDs. Before v2.1.212, event records emitted outside an active span didn’t carry trace_id or span_id.
Span hierarchy
Each user prompt starts aclaude_code.interaction root span. API calls, tool calls, and hook executions are recorded as its children. Tool spans have two child spans of their own: one for the time spent waiting on a permission decision and one for the execution itself. When the Agent tool, or legacy Task tool, spawns a subagent, the subagent’s API and tool spans nest under the parent’s claude_code.tool span.
claude -p sessions, claude_code.interaction itself becomes a child of the caller’s span when TRACEPARENT is set in the environment.
Span attributes
Every span carries the standard attributes plus aspan.type attribute matching its name. The tables below list the additional attributes set on each span. The llm_request, tool.execution, and hook spans set OpenTelemetry status ERROR when they record a failure; the other spans always end with status UNSET.
claude_code.interaction
claude_code.llm_request
Each retry attempt is also recorded as a
gen_ai.request.attempt span event with attempt and client_request_id attributes.
claude_code.tool
When
OTEL_LOG_TOOL_CONTENT=1, this span also records a tool.output span event whose attributes contain the tool’s input and output bodies, truncated at the content limit (60 KB by default) per attribute.
claude_code.tool.blocked_on_user
claude_code.tool.execution
claude_code.hook
This span is emitted only when detailed beta tracing is active, which requires ENABLE_BETA_TRACING_DETAILED=1 and BETA_TRACING_ENDPOINT in addition to the trace exporter configuration above. In interactive CLI sessions, this also requires your organization to be allowlisted for the feature. Agent SDK and non-interactive -p sessions are not gated. It is not emitted when only CLAUDE_CODE_ENHANCED_TELEMETRY_BETA is set.