OTel enhanced telemetry beta: interaction/tool spans still missing via Agent SDK query() — reopening #53954 (closed for inactivity, not fixed)
Reopening #53954, which was auto-closed by the stale-bot on 2026-05-29 for inactivity — not because a fix shipped. We hit the same gap independently and have one additional finding plus a narrower proposed fix.
Confirmation of the original report
Using @anthropic-ai/claude-agent-sdk 0.3.198 (bundled CLI 2.1.198; latest release at time of writing is 2.1.220, unconfirmed whether this is fixed there), our agent harness enables Enhanced Telemetry Beta (CLAUDE_CODE_ENABLE_TELEMETRY=1, CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1, OTEL_TRACES_EXPORTER=otlp) and only ever receives claude_code.llm_request spans on the OTLP backend. No claude_code.interaction, claude_code.tool, or claude_code.tool.execution span is ever emitted — same symptom as the original report.
New finding: this isn't limited to streamed/AsyncIterable input
query()'s public type signature (prompt: string | AsyncIterable<SDKUserMessage>, sdk.d.ts) suggests a plain string prompt might take a simpler, non-streamed path. It doesn't: our harness passes a plain string for both the initial call and every follow-up (never the AsyncIterable form), and still only gets llm_request. Per #53954's root-cause analysis, the CLI is spawned with --input-format stream-json hardcoded by the SDK regardless of which shape is passed to query() — so any fix needs to cover the plain-string call shape too, not just true multi-turn streaming input.
Proposed fix
- As suggested in #53954: route the stream-json input code path through the same span-constructing wrapper (
mr_) that the-ppositional-argument path uses, sointeraction/tool/tool.executionspans are created regardless of input transport. - Narrower alternative: when
query()is called with a plainstringprompt (the common case for headless, single-shot embedders — no multi-turn steering needed), have the SDK spawn the CLI via the non-streamed-p "<prompt>"argument path internally instead of always wrapping it in the stream-json stdin envelope. This would get the full span tree for a large class ofquery()callers with no change to the streaming-input path at all.
Impact
Any embedder of the Agent SDK that wants tool-call-level tracing (LangSmith, Datadog APM, or otherwise) currently cannot get a single correlated trace per turn — only isolated LLM-call spans, with no visibility into which tool calls happened, in what order, or with what latency/result, inside that turn.