[BUG] Built-in OTel claude_code.llm_request spans omit request_id/gen_ai.response.id on errored or aborted streaming requests
Summary
With built-in telemetry enabled (CLAUDE_CODE_ENABLE_TELEMETRY=1, OTLP trace export), the claude_code.llm_request span records the correlation IDs request_id, client_request_id, and gen_ai.response.id only when the request completes successfully (or returns a complete non-streaming error response). When a streaming request errors mid-response, times out, is aborted, or hits a connection error, the span is exported with error status but without any of these IDs — even though the request-id response header and the message_start message id had already arrived at the client. This makes failed/aborted requests impossible to correlate with server-side/provider records — precisely the requests one most needs to investigate.
Related: #79854 (closed, stale) reported the sibling symptom — spans exported UNSET on a mid-response server error. Span status now appears to be ERROR, but the correlation-ID attributes are still missing on error, which #79854 didn't cover.
Evidence
From OTLP traces over a 3-day window (span attributes only; no payloads):
- Among errored
claude_code.llm_requestspans,request_id/gen_ai.response.idare present on <0.1% (single digits out of ~17k). - The only error classes that retain the IDs return a complete non-streaming response —
rate_limit(429),output_content_filtered— and they carry the ID but nottft_ms, which decouples "has the ID" from "streamed content" and pins ID capture to "obtained a complete response object." - Transport failures ran a long time before failing (so response headers had long since arrived), yet none carry the IDs:
aborted: p50 ~50 s (max ~154 s)api_timeout: p50 ~30 minconnection_error: p50 ~3 minserver_error(503): p50 ~7.6 min- On error spans, only
duration_msis recorded (pluserror/error_class/status_code);ttft_ms,first_content_ms,output_tokens,request_id,gen_ai.response.idare all absent — indicating these attributes are committed in a single completion step that the error/abort path skips.
Expected behavior
request_id (and gen_ai.response.id once message_start arrives) should be set on the span as soon as they're known — from response headers / the first stream event — and re-affirmed in the error/abort path before span.end(), so failed and aborted streaming requests remain correlatable.
Repro (high level)
- Enable built-in telemetry (
CLAUDE_CODE_ENABLE_TELEMETRY=1,OTEL_TRACES_EXPORTER=otlp, OTLP endpoint set). - Issue a streaming request that fails after the stream begins (mid-response server error, client abort after headers, or read timeout).
- Inspect the exported
claude_code.llm_requestspan: status isERRORbutrequest_id/gen_ai.response.idare absent.
Environment
- Claude Code built-in OTel telemetry (
CLAUDE_CODE_ENABLE_TELEMETRY=1,OTEL_TRACES_EXPORTER=otlp). - Reproduces across Claude Code 2.1.x, including current latest builds — highest volume on 2.1.281 / 2.1.283 / 2.1.284 (observed 2.1.141 → 2.1.284). Still present ~70 releases after #79854 (2.1.211).