Claude Code telemetry: what redaction does not cover (2026)
Claude Code's OpenTelemetry export redacts prompts and responses by default. It does not redact who you are. Four identity attributes ship on every datapoint with no switch to turn them off, and one environment variable quietly controls two streams.
Updated on September 12, 2026
On this page
Quick answer
On 12 September 2026 I turned on Claude Code's OpenTelemetry export for a small team, left every content switch at its default, and went looking for what was actually leaving our machines. The short version: redaction covers content, not identity. All five content switches default to disabled, so prompts and responses ship as a redacted placeholder. But four attributes ride on every metric and every event with no environment variable to turn them off, and one of them is user.email. The second thing worth knowing before you enable anything: OTEL_LOG_ASSISTANT_RESPONSES falls back to OTEL_LOG_USER_PROMPTS when it is unset, so turning on prompt logging turns on response logging too unless you explicitly set the other one to zero.
The moment
We wanted a cost dashboard. That is the whole ambition. Tokens in, dollars out, per repo.
I wired the exporter on a Friday, pointed it at our collector, and then did the thing I should have done first, which was read what a single datapoint contains before deciding I was comfortable with it.
Everything below is read from Anthropic's monitoring reference on 12 September 2026, with the version numbers it gives. I have flagged what I verified against a running install and what I did not.
Two opt-ins, not one
CLAUDE_CODE_ENABLE_TELEMETRY=1 is a master switch and it is not sufficient on its own. Metrics and logs are separate exporters with separate variables, and you can run either without the other:
export CLAUDE_CODE_ENABLE_TELEMETRY=1
export OTEL_METRICS_EXPORTER=otlp # otlp | prometheus | console | none
export OTEL_LOGS_EXPORTER=otlp # otlp | console | none
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
Metrics are eight counters, and they are dull in the good way: claude_code.session.count, .lines_of_code.count, .pull_request.count, .commit.count, .cost.usage, .token.usage, .code_edit_tool.decision, .active_time.total.
Logs are where the interesting material is. That is the stream carrying user_prompt, assistant_response, tool_result, api_request, api_error, api_refusal and tool_decision events.
The intervals differ by more than an order of magnitude, and the default favours the noisier stream. OTEL_METRIC_EXPORT_INTERVAL defaults to 60000 ms. OTEL_LOGS_EXPORT_INTERVAL defaults to 5000 ms. If you enable logs on a whole team without thinking about it, you have signed up for a twelve times more frequent flush of the stream with the sensitive payload in it.
Finding 1: one switch turns on two streams
There are five content switches, all disabled by default:
Scroll to see more
| Variable | Controls |
|---|---|
OTEL_LOG_USER_PROMPTS | your prompt text |
OTEL_LOG_ASSISTANT_RESPONSES | the model's reply text |
OTEL_LOG_TOOL_DETAILS | tool parameters, full commands, error messages |
OTEL_LOG_TOOL_CONTENT | tool input and output content on spans |
OTEL_LOG_RAW_API_BODIES | the entire request and response JSON |
Four of those five behave the way you expect. The second one does not. Anthropic's wording, verbatim:
From the monitoring reference, 12 September 2026: "Enable logging of assistant response text on assistant_response events (default: disabled). When unset, falls back to the value of OTEL_LOG_USER_PROMPTS."
And again, in the attribute description for the response field:
Same page: "When OTEL_LOG_ASSISTANT_RESPONSES is unset, OTEL_LOG_USER_PROMPTS controls it instead, so set OTEL_LOG_ASSISTANT_RESPONSES=0 to keep responses redacted while prompt logging is on."
So the natural move, which is to enable prompt logging alone because you want to see what people are asking for, silently enables response logging as well. If you want prompts without responses you have to say so twice:
export OTEL_LOG_USER_PROMPTS=1
export OTEL_LOG_ASSISTANT_RESPONSES=0 # without this line, responses ship too
This is documented. It is documented in two places. It is also the default behaviour of an unset variable, which is the one state nobody audits.
Finding 2: redaction covers content, not identity
This is the finding that changed what I shipped.
Leave all five content switches off and your prompt arrives as the literal placeholder value rather than your text. Good. But the standard attributes, which are attached to every metric and every event, are a separate question, and the docs publish a table of them with a "Controlled By" column. Four rows in that column are not a variable at all:
Scroll to see more
| Attribute | Controlled by |
|---|---|
organization.id | Always included when available |
user.id | Always included |
user.email | Always included when available |
terminal.type | Always included when detected |
user.email is described as coming "from your sign-in or, in a cloud session, from the session's own credentials". There is no OTEL_METRICS_INCLUDE_EMAIL. I looked for one, and the cardinality-control table next to it lists six other include toggles, so the absence is conspicuous rather than accidental.
Two more identity attributes do have a switch, and both default to on:
OTEL_METRICS_INCLUDE_SESSION_ID=true # session.id
OTEL_METRICS_INCLUDE_ACCOUNT_UUID=true # user.account_uuid AND user.account_id
The rest of that table defaults the other way: OTEL_METRICS_INCLUDE_VERSION false, OTEL_METRICS_INCLUDE_ENTRYPOINT false, OTEL_METRICS_INCLUDE_REPOSITORY false and requiring v2.1.269 or later.
In fairness to Anthropic, user.id is explicitly documented as harmless: a random identifier generated on first run, persisted in ~/.claude.json, containing no personal information, not derived from your account, and regenerating if you delete the file. That is a reasonable design and the docs say so plainly. It is user.email and organization.id that have no off switch and are not anonymous.
None of this is a leak. It is an export you configured, going to a collector you own. The point is narrower: if your mental model is "content redacted means the telemetry is anonymous", that model is wrong, and it is wrong in a way that matters if your collector is a third-party SaaS rather than something in your own VPC.
Finding 3: three dashboards that are wrong by construction
If you build panels off these events without reading the attribute notes, three of them will be quietly incorrect no matter how long you collect.
Tool rejection rate, from tool_result. The decision_type attribute on that event is documented as "Always 'accept', since this event is only emitted after the tool runs. Rejected calls don't produce a tool result". So a rejection-rate panel built on tool_result reads 100 percent accept forever, by construction. The event you want is claude_code.tool_decision, which carries decision_type of accept or reject and a decision_source of config, hook, user_permanent, user_temporary, user_abort or user_reject. I wrote about the permission layer those sources come from in a separate log on Claude Code permissions.
Error rate, from api_error. Refusals arrive on a successful response stream rather than as an HTTP error, so api_error does not fire for them. There is a dedicated claude_code.api_refusal event. If you are tracking reliability and not counting refusals, your denominator is fine and your numerator is short.
Cost, from cost_usd. The api_request event carries both cost_usd and cost_usd_micros, an integer in millionths. Use the integer one if you are summing across thousands of requests. Floating point accumulation over a large request count is the sort of thing that produces a dashboard nobody trusts and nobody can debug. For the local alternative to all of this, where you read the cost out of the on-disk session files instead of exporting anything, I logged ccusage and its cost column last week.
Finding 4: your subprocesses do not inherit the exporter
Verbatim, and this one is easy to trip over:
From the monitoring reference: "Claude Code doesn't pass OTEL_ environment variables to the subprocesses it spawns, including the Bash tool, hooks, MCP servers, and language servers."*
So an OpenTelemetry-instrumented service that you start through the Bash tool does not pick up your endpoint or your headers. You set them in the command.
There is one exception, and it runs the other way. With the beta tracing flag on, Bash and PowerShell subprocesses automatically inherit a TRACEPARENT variable carrying the W3C trace context of the active tool execution span, so a subprocess that reads it can parent its own spans into the same trace. Configuration is not inherited. Trace context is.
Finding 5: managed settings delete your variables, asymmetrically
If you are an administrator pinning the destination, the managed settings mechanism does not merely take precedence. It removes conflicting developer-set variables at startup and logs a warning into debug output.
The scope of removal is not uniform, which is the part worth a note in your runbook:
- Set
OTEL_EXPORTER_OTLP_ENDPOINTin managed settings and Claude Code removes every developer-set per-signal endpoint. - Set
OTEL_EXPORTER_OTLP_PROTOCOLand it removes every developer-set per-signal protocol. - Set any credential variable, meaning
OTEL_EXPORTER_OTLP_HEADERS,OTEL_EXPORTER_OTLP_CLIENT_KEYorOTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE, and it removes the developer-set credential variants plus every developer-set endpoint variable, generic or per-signal.
That last rule is broader than the first two. Pinning credentials pins the destination as well, which is almost certainly what you want and is not what the variable name suggests. Exporter selectors are separate again: OTEL_LOGS_EXPORTER follows the telemetry unit while the other two merge per key from v2.1.223, so pin them explicitly if you need them pinned.
What I actually set
Cost and volume, no content, and the two identity toggles that have switches turned down:
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://collector.internal:4317
# content: leave every one of these off, and say so about responses
export OTEL_LOG_ASSISTANT_RESPONSES=0
# cardinality and identity
export OTEL_METRICS_INCLUDE_ACCOUNT_UUID=false
export OTEL_METRICS_INCLUDE_SESSION_ID=false
To confirm it is alive, the docs suggest watching for claude_code.session.count, which is emitted when a session starts. That worked for me on the first try, which is not something I get to write often.
user.email still ships. I decided that was acceptable because the collector is ours and sits inside our own network. If it were a vendor endpoint I would have wanted a processor stripping that attribute at the collector, and the OTLP specification is the right place to start reading if you need to do that, because the removal has to happen somewhere downstream of Claude Code.
What I did not verify
This is a documentation reading plus a single-machine smoke test that metrics arrive. I did not inspect a captured OTLP payload attribute by attribute to confirm user.email is present on the wire, and I did not test the managed-settings removal behaviour, because I do not have a managed deployment to test it on. Both of those are claims I am repeating from Anthropic's documentation rather than things I watched happen.
The version-gated items carry their versions because the docs give them: message.uuid and client_request_id at v2.1.214 or later, assistant_response at v2.1.193 or later, repository attributes at v2.1.269 or later, per-key exporter merging at v2.1.223 or later.
If you are enabling this for a team this week, the two lines that matter are the response fallback and the identity attributes. Everything else you will find on your own the first time a panel looks wrong.
Written by
Dani ReyesDani Reyes writes DevMoment field notes on AI dev workflow, tested on real work rather than demos.
Frequently asked questions
Does Claude Code send telemetry by default?
No. Telemetry is opt-in and off unless you set CLAUDE_CODE_ENABLE_TELEMETRY=1, and that master switch alone is not enough because metrics and logs are separate exporters controlled by OTEL_METRICS_EXPORTER and OTEL_LOGS_EXPORTER. Reading Anthropic's monitoring documentation on 12 September 2026, nothing is exported until you configure both a telemetry flag and at least one exporter plus an endpoint.
Does Claude Code telemetry include my prompts?
Only if you ask for it. Prompt content is gated behind OTEL_LOG_USER_PROMPTS, which defaults to disabled, and the prompt field is redacted to a placeholder value otherwise. The catch is the neighbouring variable: OTEL_LOG_ASSISTANT_RESPONSES falls back to the value of OTEL_LOG_USER_PROMPTS when it is left unset, so enabling prompt logging also enables assistant response logging unless you explicitly set OTEL_LOG_ASSISTANT_RESPONSES=0.
Can I turn off the email address in Claude Code telemetry?
Not through a Claude Code environment variable, as documented on 12 September 2026. The standard attributes table lists user.email as always included when available, alongside organization.id as always included when available, user.id as always included, and terminal.type as always included when detected. Six other attributes do have include toggles, such as OTEL_METRICS_INCLUDE_ACCOUNT_UUID and OTEL_METRICS_INCLUDE_SESSION_ID, both of which default to true. If you need the email removed, it has to be stripped downstream at your collector rather than at the source.
Why does my Claude Code tool rejection rate always read 100 percent accepted?
Because you are probably querying the wrong event. The decision_type attribute on claude_code.tool_result is documented as always accept, since that event is only emitted after a tool actually runs and rejected calls never produce a tool result. The event that carries real accept and reject outcomes is claude_code.tool_decision, which also reports a decision_source of config, hook, user_permanent, user_temporary, user_abort or user_reject.
Do MCP servers and Bash commands inherit Claude Code's OpenTelemetry settings?
No. Anthropic's documentation states that Claude Code does not pass OTEL_ environment variables to the subprocesses it spawns, including the Bash tool, hooks, MCP servers and language servers, so an instrumented application launched through the Bash tool will not pick up your exporter endpoint or headers and you must set them in the command itself. There is one exception in the other direction: when beta tracing is active, Bash and PowerShell subprocesses automatically inherit a TRACEPARENT variable carrying the W3C trace context of the active tool execution span.
What happens to my telemetry variables if an administrator sets managed settings?
They are removed at startup, not merely overridden, and a warning is written to debug output. The scope differs by variable. Setting OTEL_EXPORTER_OTLP_ENDPOINT in managed settings removes every developer-set per-signal endpoint, and setting OTEL_EXPORTER_OTLP_PROTOCOL removes every developer-set per-signal protocol. Setting a credential variable is broader still: it removes the developer-set credential variants plus every developer-set endpoint variable, generic or per-signal, so pinning credentials also pins the destination.
Keep reading
ccusage in 2026: what the Cost column actually measures
The Cost column in ccusage is a counterfactual: what your tokens would have cost at pay-per-use list prices, computed locally. On a Claude subscription it is not your bill, and ccusage's own introduction and Anthropic's own docs both say so. A field log on what the number measures, where the caveat is written, and where it is missing.
Claude Code Permissions: The settings.json I Actually Run (2026 Field Log)
The three arrays, the precedence rule nobody explains, the two gotchas that cost me an afternoon, and the exact settings.json allow, ask, and deny block I run now.
Claude Code hooks: the 4 I kept, the 2 I deleted (2026)
I ran Claude Code hooks daily for a month. Four earned their place, two got deleted, and one quietly made my workflow worse. The honest field log.