[BUG] interactive (TUI) sessions emit no OpenTelemetry, only `claude -p`/headless does (broke at the 2.0 release)
what's wrong
interactive claude code (the normal TUI from claude) doesn't emit any OpenTelemetry at all. no logs, no metrics, no spans. headless (claude -p) and the SDK export everything fine with the exact same config. it doesn't matter which transport i use (http/json, http/protobuf, grpc), the interactive TUI sends nothing.
i bisected it: 2.0.0 works, 2.0.10 is already broken, still broken on 2.1.195. so it broke at the 2.0 release.
practical impact: if you use OTEL to see claude code usage, your actual interactive sessions show up as nothing. only CI / headless / SDK runs get captured.
setup
- macOS (apple silicon), claude code 2.1.195 (native). also reproduced on 2.1.0 and 2.0.10 through 2.0.77 via npm under node 18.
- claude.ai oauth, team plan.
- OTLP to our own collector, and also to a dumb localhost listener so the network/collector isn't a variable.
repro
- put OTEL config in
~/.claude/settings.json(any protocol), endpoint pointed at a local listener that logs every request:
``json``
{"env":{"CLAUDE_CODE_ENABLE_TELEMETRY":"1","OTEL_LOGS_EXPORTER":"otlp","OTEL_METRICS_EXPORTER":"otlp","OTEL_TRACES_EXPORTER":"otlp","OTEL_EXPORTER_OTLP_PROTOCOL":"http/protobuf","OTEL_EXPORTER_OTLP_ENDPOINT":"http://127.0.0.1:4319","OTEL_LOG_USER_PROMPTS":"1"}}
claude -p "say hi"-> the listener gets /v1/logs (with the transcript), /v1/traces, /v1/metrics. works.- open
claudeinteractively, send one prompt, exit -> the listener gets nothing.
same machine, same settings, same collector. the only thing that changes is -p vs the TUI.
what i checked
- headless sent real payloads (20-55KB of /v1/logs, plus traces and metrics). interactive sent 0 requests for the same kind of turn.
- the interactive turn actually ran. it replied, burned tokens, and the assistant message is in the local .jsonl. so it's not "nothing happened".
- tried all three transports interactively (on 2.0.77): http/json and http/protobuf both sent 0 requests, and grpc never even opened a connection to :4317. so it's not a protocol issue.
OTEL_LOG_LEVEL=debugprints nothing in the interactive case. no error, no connection attempt, nothing on stderr.- collector is fine (manual OTLP POST returns 200, headless works end to end). and it's not the subprocess OTEL_* stripping from #56153, the parent process is configured correctly and headless works from it.
bisect
ran each version through one interactive turn against a local listener. kept 1.x and 2.0.x on the same node 18 so the node version isn't a variable.
| version | interactive TUI otel | notes |
|---|---|---|
| 1.0.128 | exports | |
| 2.0.0 | exports | last version that works (turn ran, 3 OTLP requests) |
| 2.0.10 | 0 requests | turn ran |
| 2.0.19 | 0 requests | turn ran |
| 2.0.28 | 0 requests | turn ran |
| 2.0.77 | 0 requests | turn ran (19k tokens), all 3 transports dead |
| 2.1.0 | 0 requests | |
| 2.1.195 | 0 requests | current, -p exports fine |
so it broke between 2.0.0 and 2.0.10, i.e. the 2.0 release. couldn't pin the exact patch: npm is missing 2.0.4/2.0.6/2.0.7 and some of those builds wouldn't run under the node versions i had.
my guess at the cause
looks like OTEL only gets set up on the headless/print path and never on the interactive one, so nothing gets emitted no matter the transport. fix would be to initialize (and flush on exit) the same providers for the interactive entrypoint that -p already uses.
related issues
- #71312 - probably the same bug, but seen interactively and attributed to "exporter not compiled in / broken since 2.1.113". headless works fine on 2.1.195 and my bisect points to 2.0.x, so i think the real split is interactive vs
-p, and it's older than 2.1.113. - #64436 - says interactive works over grpc on 2.1.159. i couldn't get 2.1.159 to run locally to confirm, and grpc was dead in my interactive tests on 2.0.77. the one thing i couldn't verify is whether a current 2.1.x interactive session exports over grpc - if it does, the breakage is interactive-http-only rather than all of interactive, and that's the only gap between this and #64436.
- #39471 / #63171 are cowork-specific. #56153 is the subprocess env thing, which isn't this.