Omniscio

OmniscioGuidesClaude Code OpenTelemetry

Claude Code OpenTelemetry: what leaves the process, and how to point it somewhere

Claude Code can report its own usage, cost and tool activity through OpenTelemetry. The catch is that the export is off until you configure it, the configuration has to live in the right file, and a few flags turn the actual conversations into log records. This page covers the pipe end to end: the variables, where they must go, exactly what is inside each metric and event, how to keep prompt and tool content out, the beta tracing path, and how to prove a collector is receiving anything.

Sources read October 27, 2026. By the Omniscio team. Updated .

Start here: which half of "telemetry" you are dealing with

Two unrelated streams leave a Claude Code process, and people mix them up constantly. One is Anthropic's own operational telemetry: usage metrics and error reports. The metrics go to Anthropic and never include your code, prompts or file paths; error reports go to a third-party error tracking service, with known patterns of secrets, file paths and email addresses redacted first. The other is your OpenTelemetry export: metrics, events and optional traces sent to a collector you choose, and it stays off until you set it up. Turning one off has nothing to do with the other, which is why "how do I disable Claude Code telemetry" has two different answers below.

If your question is where your own token counts and spend appear in the app and in Claude Code, that is a different page: the token usage guide covers /usage, /context, a status line and the analytics dashboard, and treats OpenTelemetry as one of those four places. This page is the one that assumes you have chosen OpenTelemetry and now have to make it work.

Turning it on

One variable enables collection, and it is required: CLAUDE_CODE_ENABLE_TELEMETRY=1. On its own it exports nothing, because both exporters are optional and you configure only the ones you want. Metrics go through OTEL_METRICS_EXPORTER, which accepts otlp, prometheus, console or none; events and logs go through OTEL_LOGS_EXPORTER, which accepts otlp, console or none. Both take a comma-separated list, so console,otlp is legal and is the pair most people want while bringing a collector up.

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
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer replace-me"

That block is safe as written because the collector is on your own machine. Anywhere else, use https://: a real token in OTEL_EXPORTER_OTLP_HEADERS travels in the request, so an http:// endpoint on a remote host puts it on the wire in clear text.

The protocol has no default, and that is the usual first failure

Claude Code ships no default protocol for an otlp exporter, so OTEL_EXPORTER_OTLP_PROTOCOL is not optional in practice. It takes grpc, http/json or http/protobuf, and it applies to every signal. By OpenTelemetry's own convention a collector listens for gRPC on 4317 and for HTTP on 4318 with a path such as /v1/metrics, so pointing one at the other's port looks like a network failure rather than a mismatch.

Per-signal overrides, and how headers merge

Every generic variable has a per-signal sibling: OTEL_EXPORTER_OTLP_METRICS_ENDPOINT, OTEL_EXPORTER_OTLP_LOGS_ENDPOINT, their _PROTOCOL versions, and their _HEADERS versions. A per-signal endpoint or protocol replaces the generic one for that signal. A per-signal headers variable behaves differently and is easy to get wrong: it is merged with the generic OTEL_EXPORTER_OTLP_HEADERS for that signal, not substituted for it, so both sets of headers are sent.

Send metrics and logs to different places

That is supported and is the normal shape when a metrics backend and a log store are separate products. Set the generic endpoint for the common case, then override the endpoint and protocol for the signal that goes elsewhere.

Intervals are for debugging, not for production

Metrics batch every 60 seconds by default and logs every 5 seconds, which is why a fresh setup looks broken for the first minute. Lower them while you are testing with OTEL_METRIC_EXPORT_INTERVAL and OTEL_LOGS_EXPORT_INTERVAL, both in milliseconds, and put them back afterwards: a short interval on a fleet is a lot of small requests. The console exporter with a one-second interval is the fastest way to see the shape of the data before a collector exists.

Where the variables have to live

This is the part that wastes the most time, because a plausible-looking configuration can be silently discarded. Claude Code deliberately ignores the OpenTelemetry exporter variables in a repository's .claude/settings.json and .claude/settings.local.json, on the reasoning that a checked-out repository should not be able to turn telemetry on, choose where it goes, or decide to capture prompt content. It drops each one and logs a warning visible with claude --debug. That rule is enforced on v2.1.282 or later; below that floor a project or local file could still set these variables, so check the version before relying on it.

The ignored set is wider than the four obvious variables. It covers CLAUDE_CODE_ENABLE_TELEMETRY, the enhanced-telemetry beta pair, the three exporter selectors, the four content flags, every OTEL_EXPORTER_OTLP_* name ending in _ENDPOINT, _HEADERS, _PROTOCOL, _CERTIFICATE, _CLIENT_KEY or _INSECURE, the Prometheus host and port variables, OTEL_LOG_RAW_API_BODIES, and the detailed beta tracing pair.

There is exactly one exception, and it runs the other way: values that turn something off still apply from a project or local file. none for any of the three exporter selectors, and an off value such as 0 for OTEL_LOG_USER_PROMPTS, OTEL_LOG_TOOL_CONTENT or OTEL_LOG_TOOL_DETAILS, are honoured, and can disable a signal or a content flag that would otherwise be on. There is one limit on that power: such a value overrides the same variable in your user settings, but not one that your shell, a --settings file or managed settings sets. And a project file cannot switch anything on.

So the places that work are your shell, your user settings file, managed settings, and a file passed with --settings. Between the first two, a value in the settings file wins over the same variable exported in your shell. An empty string cancels a shell export, which is a tidier way to switch one off for every session than editing a profile. For which file is the user settings file on a given machine, the settings location guide has the answer; nothing here changes it.

What managed settings do that a user file cannot

An organization that has to be sure where telemetry lands sets the variables in managed settings, and Claude Code then removes the conflicting developer-set variables at startup. Setting OTEL_EXPORTER_OTLP_ENDPOINT removes every developer-set per-signal endpoint, so no developer-set per-signal endpoint can override it. An administrator can still route one signal elsewhere, by setting that signal's own variable in managed settings. Setting the protocol removes the per-signal protocols. Setting a credential, whether OTEL_EXPORTER_OTLP_HEADERS, _CLIENT_KEY or _CLIENT_CERTIFICATE, removes the developer-set per-signal credentials and every developer-set endpoint, generic or per-signal, because otherwise those credentials would travel to a collector managed settings did not choose.

The exporter selectors are the exception to that removal, and worth knowing before a rollout: OTEL_METRICS_EXPORTER, OTEL_LOGS_EXPORTER and OTEL_TRACES_EXPORTER follow ordinary per-key precedence, so a developer can still set one to none or to console. If the selectors must be locked too, set them in managed settings as well. The endpoint and credential removal arrives in v2.1.223 or later; before v2.1.217 every variable followed per-key precedence independently, so a shell endpoint could redirect a signal away from the managed collector.

Managed settings are one file per operating system: /Library/Application Support/ClaudeCode/managed-settings.json on macOS, /etc/claude-code/managed-settings.json on Linux and WSL, and C:\Program Files\ClaudeCode\managed-settings.json on Windows. The telemetry variables sit in its normal env block, exactly as they would in a user settings file.

Your own subprocesses do not inherit them

A detail that surprises people building on top of Claude Code: it does not pass OTEL_* variables to the subprocesses it spawns, including the Bash tool, hooks, MCP servers and language servers. An OpenTelemetry-instrumented program you run through the Bash tool therefore does not inherit your collector endpoint or headers, and has to be given them in the command itself. The one exception is trace context, covered under traces.

What actually leaves the process

Three signal types leave, and each is a different protocol in OpenTelemetry terms. Metrics are time series. Events travel over the logs protocol, so an events-only setup configures OTEL_LOGS_EXPORTER in place of the metrics exporter, and still needs a protocol and an endpoint. Traces are the optional third, still in beta, covered in their own section.

The eight metrics

Claude Code exports eight, and knowing the exact names matters because every dashboard and alert you build keys on them.

MetricWhat it counts
claude_code.session.countCLI sessions started, with a start_type of fresh, resume, continue or agents_view
claude_code.lines_of_code.countLines of code added or removed, split by type and tagged with the model that changed them
claude_code.pull_request.countPull requests or merge requests Claude Code created through a shell command or an MCP tool
claude_code.commit.countGit commits created through Claude Code
claude_code.cost.usageEstimated session cost in USD, incremented after each API request
claude_code.token.usageTokens used, split into input, output, cacheRead and cacheCreation
claude_code.code_edit_tool.decisionAccept or reject decisions on Edit, Write and NotebookEdit, with where the decision came from
claude_code.active_time.totalActive time in seconds, split into user keyboard time and cli processing time

With prometheus as the only exporter, Claude Code omits the USD, tokens and s units so the output stays valid Prometheus text format. Combine exporters, such as otlp,prometheus, and the units come back.

The events that carry the detail

Events are where the per-turn narrative lives. Each one is a log record whose name starts claude_code., and the seven that matter for a dashboard are these.

  • claude_code.user_prompt: a prompt was submitted. Carries its length, and its text only under the prompt gate.
  • claude_code.assistant_response: a model response arrived. Carries response_length, the model and a redacted response.
  • claude_code.tool_result: a tool finished running. Carries success, duration_ms, sizes, and the permission decision's source. A rejected call never produces one.
  • claude_code.tool_decision: a permission decision was made. Carries the tool, accept or reject, and where the decision came from: config, hook, user_permanent or user_temporary, plus the reject-only user_abort and user_reject.
  • claude_code.api_request: one API call, with tokens, estimated cost_usd, duration and cache counts.
  • claude_code.api_error: a request that failed, with its status code and an attempt count.
  • claude_code.api_refusal: a refusal, which arrives on a successful stream and therefore never fires api_error.

That is seven of them, not the whole set. The export also carries claude_code.safety_event, claude_code.auth, claude_code.permission_mode_changed, claude_code.mcp_server_connection, claude_code.api_retries_exhausted, claude_code.compaction and claude_code.subagent_completed, which is the half an audit trail leans on. Anthropic documents the complete set, including the hook, plugin, skill, compaction and retention events, on its monitoring page.

The attributes every record shares

All metrics and events are stamped with the same standard attributes, and they are what make the numbers attributable: session.id, organization.id, user.account_uuid, user.account_id, user.email, terminal.type, and user.id, which is a random identifier generated on first run and stored in ~/.claude.json, containing no personal information and derived from nothing about your account. On a cloud session the set also carries ccr.session.id, and three more are opt-in rather than on by default: app.version, app.entrypoint and the repository attributes below.

Resource attributes identify the process rather than the turn: service.name is claude-code for a terminal session and claude-code-desktop for a local session started from the Code tab in the Claude Desktop app, alongside service.version, os.type, os.version, host.arch and, on WSL, wsl.version. The meter name is com.anthropic.claude_code. A pipeline or dashboard that filters on service.name = claude-code will miss every Desktop Code-tab session unless you add the second value.

Team and cost-centre tags

OTEL_RESOURCE_ATTRIBUTES is how a multi-team organization tells its people apart: comma-separated key-value pairs such as department=engineering,team.id=platform,cost_center=eng-123. Claude Code puts them on the resource block and on every datapoint and event record, so most metrics backends can group by them directly. Except for the vcs.* keys below, a custom key never overrides a standard attribute such as user.id: when the two collide, the built-in value stays.

The format is strict and fails quietly. Values cannot contain spaces; only US-ASCII is allowed, excluding control characters, whitespace, double quotes, commas, semicolons and backslashes; anything outside that range must be percent-encoded; and quoting a value does not help, since org.name="My Company" arrives with the quote characters included. A value with a space in it is the nastiest case: Claude Code then copies none of the keys onto events or datapoints and reports no error at all.

Repository identity, and the one place it does not go

Setting OTEL_METRICS_INCLUDE_REPOSITORY=true tags metrics and events with the session repository's identity, derived once per session from its origin remote: vcs.repository.url.full, vcs.owner.name, vcs.repository.name and vcs.provider.name. It needs v2.1.269 or later, the values are lowercased, and credentials, query strings and fragments from the remote never appear in them. They are omitted when there is no origin, when the remote is not URL-shaped, or when the only enclosing repository is your home directory. Those attributes flow to your exporters only: Anthropic's own telemetry drops every vcs.* key.

Cardinality, and how to turn some of it off

Every attribute is a label, and labels cost storage. Six switches decide which ones are attached to metrics, and their defaults are worth reading once because they are not consistent: OTEL_METRICS_INCLUDE_SESSION_ID defaults true, OTEL_METRICS_INCLUDE_ACCOUNT_UUID defaults true, OTEL_METRICS_INCLUDE_RESOURCE_ATTRIBUTES defaults true, while OTEL_METRICS_INCLUDE_VERSION, OTEL_METRICS_INCLUDE_ENTRYPOINT and OTEL_METRICS_INCLUDE_REPOSITORY default false. Turning the resource-attribute one off is the documented way to send your team tags in the resource block only, without paying for them as a label on every series.

Content: what is off by default, and the four gates that turn it on

This is the section to read before enabling anything for a team, because the defaults are protective and the switches are not fine-grained in the way people assume. Prompt content is not collected by default: only prompt_length is recorded. Assistant response text is not collected either, only response_length. Tool input arguments are not logged.

  • OTEL_LOG_USER_PROMPTS=1 puts the prompt text on user_prompt events, in two attributes: prompt and, from v2.1.287, prompt_text. The duplicate exists because a backend that stores dotted names as nested objects reads prompt.id as an object and can lose the string, so it needs prompt_text.
  • OTEL_LOG_ASSISTANT_RESPONSES=1 puts the response text on assistant_response events. If you leave it unset it follows OTEL_LOG_USER_PROMPTS, so setting OTEL_LOG_ASSISTANT_RESPONSES=0 is how you keep responses redacted while prompts are being logged.
  • OTEL_LOG_TOOL_DETAILS=1 logs tool parameters and input arguments: Bash commands, MCP server and tool names, skill names, subagent types, and file paths. It also stops the cost and token counters redacting agent, skill, plugin and MCP names to custom or third-party. Fields such as full_command are emitted untruncated.
  • OTEL_LOG_TOOL_CONTENT=1 is the heaviest and applies to traces: it puts raw file contents, Bash output, and what MCP tools, WebFetch and WebSearch returned into a tool.output span event. Results from MCP, WebFetch and WebSearch need v2.1.283 or later.

The one that captures everything at once

Setting OTEL_LOG_RAW_API_BODIES emits the full Anthropic Messages API request and response JSON as api_request_body and api_response_body events. Those bodies contain the entire conversation history (the system prompt, every prior user and assistant turn, and tool results), so Anthropic states plainly that enabling it implies consent to everything the other content flags would reveal. Extended-thinking content is always redacted from those bodies regardless of any other setting. The value decides the delivery: 1 puts the body inline on the event, truncated at the content limit, while file:<dir> writes untruncated .request.json and .response.json files and puts a body_ref path on the event instead. Those files never pass through your collector, so the collector-side rule below cannot remove them: ship the directory with a log collector or sidecar, and secure or delete it.

Every content value is capped

Content-bearing attributes are truncated at 60 KB by default, which is 61440 UTF-16 code units, marker included. That default is chosen to fit backends that cap an attribute value at 64 KB. CLAUDE_CODE_OTEL_CONTENT_MAX_LENGTH raises or lowers it, and if you also set an OpenTelemetry SDK limit such as OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT below it, Claude Code truncates at the smaller value so the truncation marker itself still fits. A truncated attribute is accompanied by <attribute>_truncated and <attribute>_original_length.

Drop the content at the collector, not in the client

The pattern to copy is to leave the client honest and filter at the collector, where the rule is visible to whoever audits the pipeline. If prompt text must never be stored, the OpenTelemetry Collector's attributes processor deletes both attributes in the pipelines that list it, which is the documented shape and the one to copy.

processors:
  attributes/drop-prompt-text:
    actions:
      - key: prompt
        action: delete
      - key: prompt_text
        action: delete

One attribute you cannot drop at the source is worth knowing about: when you are authenticated through OAuth, user.email is included in telemetry attributes, and Anthropic's guidance is to filter or redact it in your own backend if that is a concern. It is sent only to the OTel endpoint you configure and never to Anthropic.

Traces, which are still in beta

Distributed tracing links each prompt to the API requests and tool executions it caused, so one prompt is one trace in a tracing backend. It is off by default and needs two switches, which is the first thing people miss: CLAUDE_CODE_ENABLE_TELEMETRY=1 and CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1 (ENABLE_ENHANCED_TELEMETRY_BETA is also accepted), plus OTEL_TRACES_EXPORTER set to otlp or console. Traces reuse the same endpoint, protocol, headers and mTLS configuration as the other signals.

The span tree is one root per prompt. claude_code.interaction is the root; claude_code.llm_request and claude_code.tool hang off it; a tool span has two children of its own, claude_code.tool.blocked_on_user for time spent waiting on a permission decision and claude_code.tool.execution for the run itself. When the Agent tool spawns a subagent, that subagent's API and tool spans nest under the parent's tool span, so a fan-out reads as one tree rather than several disconnected traces.

Spans redact prompt text, tool input and tool content by default, under the same three flags as the events. The attributes that make traces worth having are not content: ttft_ms time to first token, first_content_ms, duration_ms including retries, attempt, stop_reason, error_class, speed, effort, and the cache token split.

Trace context follows your commands

While tracing is on, Bash and PowerShell subprocesses inherit a TRACEPARENT environment variable carrying the active tool span's W3C trace context, so a script that reads it can parent its own spans into the same trace. The same header is sent on model requests and outbound HTTP MCP requests when Claude Code talks to the Anthropic API, and only then by default, because some proxies reject unknown headers. Behind a custom ANTHROPIC_BASE_URL, set CLAUDE_CODE_PROPAGATE_TRACEPARENT=1 if you want it sent anyway.

In -p and Agent SDK sessions the flow reverses: Claude Code reads TRACEPARENT and TRACESTATE from its own environment, so an embedding process can pass its trace in and have Claude Code's spans appear as its children. Interactive sessions deliberately ignore inbound values so they do not inherit whatever a CI runner or container left set.

The deeper beta, and its two extra requirements

A more detailed mode adds a claude_code.hook span and a set of content attributes that are explicitly not part of the stable schema. It needs ENABLE_BETA_TRACING_DETAILED=1 and BETA_TRACING_ENDPOINT together, a pair that also changes where your logs and traces go, and in an interactive session your organization additionally has to be allowlisted for the feature. Agent SDK and -p sessions do not need the allowlist. Both variables are ignored in project and local settings.

Prove it works, and read the failures

One check settles whether metrics are arriving: start a session and look for claude_code.session.count in your backend. For an events-only setup, submit a prompt and look for claude_code.user_prompt. If neither appears, the answer is in a log rather than on the screen.

Start Claude Code with claude --debug-file <path> and read what it wrote there. Export failures appear as [3P telemetry] errors, where 3P means third-party, meaning your exporter. A line prefixed [Anthropic telemetry] describes Anthropic's own separate operational telemetry and does not indicate a problem with your setup, which is the misreading that sends people chasing the wrong thing for an hour.

The four failures worth checking first

  • Nothing arrives and nothing is logged. The variables are not reaching the process, or they were set in a project settings file. Check /status, and check which file you edited.
  • A connection error at the collector. Almost always the protocol: gRPC at an HTTP port, or no protocol set at all. There is no default, so an unset OTEL_EXPORTER_OTLP_PROTOCOL is a real cause.
  • A hook or a script inside a session cannot export. Claude Code does not pass OTEL_* to its subprocesses. Set them in the command that needs them.
  • Headers stop working after a token rotation. A static header in the settings file goes stale; that is the case otelHeadersHelper exists for, below.

Rotating credentials without editing a file

When a collector token rotates or comes from a vault, otelHeadersHelper in the settings file names a command that prints a JSON object of string header values on stdout. It runs at startup and periodically afterwards, every 29 minutes by default and adjustable with CLAUDE_CODE_OTEL_HEADERS_HELPER_DEBOUNCE_MS, and it is allowed in any settings file, unlike the exporter variables themselves. It only applies to the http/protobuf and http/json protocols: with grpc, only the static header variables are used.

When the helper fails, exports stop entirely for that session and the failure is announced rather than swallowed. In an interactive session you get one warning, otelHeadersHelper failed; telemetry is not being exported, with the detail after See /status:; in a -p run the same failure appears on stderr as otelHeadersHelper failed (OpenTelemetry export headers unavailable). The script has to exit 0 within 30 seconds and print valid JSON, and the fix belongs in the script rather than in the settings file.

Certificates and mutual TLS

Collector client certificates depend on the protocol. For http/protobuf and http/json the variables are CLAUDE_CODE_CLIENT_CERT, CLAUDE_CODE_CLIENT_KEY and optionally CLAUDE_CODE_CLIENT_KEY_PASSPHRASE, with the collector's CA trusted through NODE_EXTRA_CA_CERTS. For grpc they are OTEL_EXPORTER_OTLP_CLIENT_KEY and OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE, with the CA in OTEL_EXPORTER_OTLP_CERTIFICATE, and per-signal variants exist if one signal needs its own certificate. A TLS-inspecting proxy in front of a collector fails the same way it fails everywhere else; the connection errors guide has that fix and the NODE_EXTRA_CA_CERTS detail.

Turning it off, and the other telemetry

"Turn Claude Code telemetry off" means two different things, and the query behind this page is usually about one of them. Anthropic's own operational telemetry is the default-on stream; your OpenTelemetry export is the opt-in one.

  • Your export stops when CLAUDE_CODE_ENABLE_TELEMETRY is unset, or when an exporter selector is set to none. Because none is one of the values a project settings file is still allowed to set, a repository can turn an individual signal off in the checkout. That holds only when nothing above it sets the same selector: a shell export, a --settings file or managed settings wins.
  • Anthropic's usage metrics are switched off with DISABLE_TELEMETRY=1. They cover latency, reliability and usage patterns, and they never include your code, prompts or file paths.
  • Anthropic's error reports are switched off with DISABLE_ERROR_REPORTING=1. They carry error messages and stack traces from Claude Code's internals, with known patterns of secrets, paths and email addresses redacted before anything leaves the machine.
  • Everything non-essential at once is switched off with CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC, with one consequence worth knowing in advance: it also disables feature-flag evaluation, which can make Remote Control unavailable. DISABLE_ERROR_REPORTING does not.

On Amazon Bedrock, Google Cloud's Agent Platform, Microsoft Foundry and Claude Platform on AWS, error reporting, telemetry and bug reporting are already disabled by default. Session quality surveys and the WebFetch domain safety check are the documented exceptions and run regardless of provider.

Cloud sessions and Claude Tag channel sessions need their own route, because a settings file on a developer's machine does not configure them. The two options are the env block of server-managed settings, which reaches cloud sessions but not Claude Tag channels, and the cloud environment's own variables, which reach Claude Tag channels too. Anthropic is explicit that a credential must not go in an environment's variables, because anyone who uses that environment can read them; if the collector needs a token, configure the whole export through server-managed settings instead.

In the Claude Desktop app, Code-tab sessions read the managed settings that reach them, and Cowork is configured separately in the admin console's data and privacy settings, where the OpenTelemetry form applies to Cowork sessions only. Neither the terminal CLI nor the Code tab exports to a collector set there.

Pointing it at a backend

Because the export is plain OTLP, any backend with an OTLP or Prometheus receiver works, which is why the searches that lead here name Grafana, Datadog and Langfuse. Anthropic's own guidance on choosing one is about shape rather than brand: time-series or columnar stores for rates and aggregates, log aggregation for full-text search over events, distributed tracing backends for span waterfalls and latency, and a full-featured observability platform when you want metrics, logs and traces correlated in one place. Organizations that need daily, weekly and monthly active-user counts should pick a backend that answers unique-value queries efficiently.

Two ready-made starting points exist and are worth naming rather than paraphrasing. Anthropic's ROI measurement repository publishes Docker Compose configurations, Prometheus and OpenTelemetry setups, and report templates, and a separate AWS guidance repository carries Claude Code monitoring guidance for Bedrock.

Prometheus, if you would rather scrape

Setting OTEL_METRICS_EXPORTER=prometheus exposes metrics for scraping at http://localhost:9464/metrics and needs no collector. On a self-hosted environment the session binds that port only at the runner's default capacity of one; above that the runner re-exposes the counters and gauges on its own endpoint.

Getting the numbers right once they arrive

Three accounting details decide whether your dashboards agree with each other. First, cost is an estimate: Claude Code labels it approximate and points at the provider (Claude Console, Bedrock or Google Cloud's Agent Platform) for official billing. Second, input_tokens excludes the prompt cache; tokens read from and written to the cache appear only under cacheRead and cacheCreation. To match the OpenTelemetry GenAI semantic convention, which counts cache traffic inside the input total, add all three together. Third, a streaming response is counted exactly once even when a gateway behind ANTHROPIC_BASE_URL streams usage across several frames.

Retry accounting is the other place people misread a graph. Claude Code retries internally and emits a single claude_code.api_error only after it gives up, so the event is already the terminal signal rather than one attempt among many. The attempt attribute carries the count: CLAUDE_CODE_MAX_RETRIES defaults to 10 and is capped at 15, so an exhausted request shows 11 by default; with CLAUDE_CODE_RETRY_WATCHDOG set the default is higher and there is no cap. A lower number can still mean exhaustion, because the count restarts at 1 each time Claude Code re-issues a request after a streaming failure. To tell a session that recovered from one that stalled, group by session.id and look for a later api_request.

Attribution is on the counters and on api_request, api_error and api_refusal: agent.name, skill.name, plugin.name, marketplace.name, mcp_server.name and mcp_tool.name, alongside model, speed, effort and a query_source of main, subagent or auxiliary. Most of those names are redacted to custom or third-party unless OTEL_LOG_TOOL_DETAILS=1 is set, which is the trade to understand before you build a per-skill cost report.

Using events as an audit trail

The events are documented as Claude Code's audit data source, and on a session signed in to a Claude account they carry identity attributes that tie a tool call or a permission decision back to the user who triggered it. On a direct API key, or on Bedrock, Google Cloud's Agent Platform or Foundry, there is no Claude account in the session and only user.id and session.id are populated, so attach identity yourself with OTEL_RESOURCE_ATTRIBUTES. Point OTEL_EXPORTER_OTLP_LOGS_ENDPOINT at a SIEM's OTLP receiver, or at a collector that forwards to it, and turn on OTEL_LOG_TOOL_DETAILS when MCP and Bash activity has to be auditable rather than counted. On Bedrock, one correlation detail differs: request_id comes from the x-amzn-requestid header rather than request-id, on v2.1.282 or later.

Omniscio and OpenTelemetry

Omniscio is the desktop app we make for running many Claude Code sessions at once. It has no OpenTelemetry feature: no exporter setting, no collector field, no telemetry panel. Nothing in the app reads, sets or interprets an OTEL_* variable, and this page is not selling one. What you configure here is Claude Code's own export, and the app is simply where some of those sessions run.

There is one honest consequence of that, and it is the opposite of how the app treats provider credentials. A session Omniscio starts inherits the environment the app itself was launched with, minus a fixed list of variables the app strips for safety, and the OTel variables are not on that list. So a configuration exported in the shell you launched the app from, or written into your user settings file, reaches the sessions it starts. A configuration you only exported in some other terminal does not, for the same reason an editor launched from the Dock does not see a shell export.

What the app does not do is read your OTLP pipeline. Its own token and spend numbers are computed from the session transcript and cost records it stores locally, which means the app's usage view and your collector are two independent pictures of the same work. If you are trying to reconcile them, expect them to differ by exactly the things this page describes: cache tokens counted differently, costs estimated locally, and any session that ran outside the app missing from its totals.

If your question is where those in-app numbers come from, the token usage guide covers it. If you are comparing ways to run several sessions at once, the features page lists what the app does and the download page has the installers.

Sources

Every variable, message and number on this page rests on one of these. The date beside each is the day we read it.

  • Monitoring with OpenTelemetry, for the quick start, the configuration variables, mTLS, cardinality, traces and their span attributes, dynamic headers, multi-team attributes, Anthropic's cloud sessions, the metrics and events, the interpretation guidance, the SIEM section, backends and service information. Read October 27, 2026.
  • Settings reference, for the env rules, the variables Claude Code ignores in project and local settings, and otelHeadersHelper. Read October 27, 2026.
  • Errors, for the otelHeadersHelper failed warning and what to do about it. Read October 27, 2026.
  • Data usage, for Anthropic's own telemetry services, the switches that turn them off, and the provider defaults. Read October 27, 2026.
  • Managed settings, for the per-operating-system file paths. Read October 27, 2026.
  • Claude Code ROI measurement guide, for the ready-made Docker Compose and Prometheus setups Anthropic points at. Read October 27, 2026.
  • Claude Code monitoring on Amazon Bedrock, for the AWS-specific guidance. Read October 27, 2026.
  • One capture run on this machine on October 27, 2026: claude --version, which printed 2.1.311 (Claude Code).

This part of the product moves with every release: the monitoring page carries version notes on the managed-settings removal behaviour, the repository attributes and the request-id fallback, so a copy of this page from a few months back is worth re-reading against the live pages.

Common questions

What is the single variable that turns OpenTelemetry on for Claude Code?

CLAUDE_CODE_ENABLE_TELEMETRY=1. It is required, but it does nothing on its own: both exporters are optional, so you also choose at least one with OTEL_METRICS_EXPORTER or OTEL_LOGS_EXPORTER, and set OTEL_EXPORTER_OTLP_PROTOCOL because Claude Code has no default protocol for an otlp exporter.

Why does telemetry set in my repository's .claude/settings.json do nothing?

Because Claude Code ignores the OpenTelemetry exporter variables in project and local settings, so a checked-out repository cannot turn telemetry on, choose where it goes, or capture content. Only values that turn something off still apply from those files, such as none for an exporter selector or 0 for a content flag. Set the rest in managed settings, your user settings, or your shell.

Does Claude Code send my prompts to Anthropic when telemetry is on?

No. OpenTelemetry data goes only to the endpoint you configure, never to Anthropic. Prompt content is also off by default: only the prompt length is recorded until you set OTEL_LOG_USER_PROMPTS=1. That is separate from Anthropic's own operational telemetry, which never includes your code, prompts or file paths and is switched off with DISABLE_TELEMETRY=1.

How do I turn Claude Code telemetry off completely?

Two different things are being turned off. Your own export stops when you stop setting CLAUDE_CODE_ENABLE_TELEMETRY and the exporter variables, or set an exporter selector to none. Anthropic's own operational telemetry and error reports have their own switches, DISABLE_TELEMETRY and DISABLE_ERROR_REPORTING, and CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC turns off all non-essential traffic at once.