[BUG] Canceled foreground subagent provider request has no terminal llm_request span
Summary
On Agent SDK 0.3.289 / bundled Claude Code 2.1.289 on macOS x64, stopping a foreground custom subagent after a real provider-authored thinking block produced:
- the expected public
stopTask(taskId)acknowledgement andtask_notification: stopped; - a correlated
claude_code.tool.executionspan ending withAbortError; - successful later parent
claude_code.llm_requesttelemetry and same-query parent continuation; - no exported
claude_code.llm_requestterminal span attributable to the canceled child request in the retained telemetry through owning process disposal.
This appears related to https://github.com/anthropics/claude-code/issues/98192, but our captured case lacks the child's terminal span itself, not only its request-ID attributes. Please investigate cancellation/finalization/export coverage for foreground child requests and emit their terminal request-lifetime status with the IDs already observed from provider headers/stream messages.
Public configuration
CLAUDE_CODE_ENABLE_TELEMETRY=1
CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1
OTEL_TRACES_EXPORTER=otlp
OTEL_LOGS_EXPORTER=otlp
OTEL_METRICS_EXPORTER=none
OTEL_EXPORTER_OTLP_PROTOCOL=http/json
OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:<owned-collector-port>
OTEL_TRACES_EXPORT_INTERVAL=1000
OTEL_LOGS_EXPORT_INTERVAL=1000
OTEL_LOG_USER_PROMPTS=0
OTEL_LOG_ASSISTANT_RESPONSES=0
OTEL_LOG_TOOL_DETAILS=0
OTEL_LOG_TOOL_CONTENT=0
OTEL_LOG_RAW_API_BODIES=0
Official SDK options included includePartialMessages:true and forwardSubagentText:true. The custom agent ran in the foreground with maxTurns:24; the main query used claude-haiku-5-5. SDK internals and native binaries were not inspected or modified. Capture was passive at public SDK/hooks/OTLP boundaries.
The embedding application supplies a local plugin/custom agent and an in-process MCP server. The measured child workload used no tools: it was asked to write a deliberately long generic response so interruption could occur during model output. This report describes the actual embedded execution, not a claim that a standalone minimal SDK-only reproduction has already passed.
Observed timeline
Identifiers below are redacted consistently; values are relative to the actual public stopTask invocation.
| Time | Observation |
|---:|---|
| -69 ms | Real child assistant thinking block; provider request_id and message ID present; exact parent_tool_use_id joins the foreground Agent call |
| 0 ms | Public stopTask(child-task) invoked while exact native task is running |
| +7.26 ms | Agent claude_code.tool.execution span ends, success:false, status ERROR, error_class:AbortError |
| +13 ms | Public stop promise resolves |
| after stop | Exact stopped notification; parent makes another successful request and returns within the same query |
| +11.35 s | Final observation/readback ends; no later observed child assistant/stream frames or tool admissions |
| teardown | Actual owning runtime disposal and observed descendant closure; collector remains available through disposal and then closes |
The collector retained normal parent claude_code.llm_request spans (before and after the child), Agent tool spans and its AbortError/end span, all on the expected trace. It was not an empty or globally broken exporter. The canceled child request has a provider request ID in its forwarded assistant frame, but neither an agent_id-joined child terminal LLM span nor a corresponding terminal child LLM span under the Agent span was retained.
Reproduction steps
- Enable the public OTLP configuration above with an owned loopback collector.
- Start one SDK query and dispatch one native foreground custom Agent.
- Ask the child for a long tool-free response.
- Wait for an actual forwarded child thinking block, preserving its request/message IDs and exact originating Agent call/task identity. Do not use elapsed time alone as evidence that a model request started.
- Invoke public
Query.stopTaskfor that exact task. - Keep consuming the same query until the parent successfully continues. Allow telemetry export grace and keep the collector alive through owning-process disposal.
- Inspect all exported
claude_code.llm_requestspans, bothagent_ididentity and Agent-span ancestry. Compare with the actual child provider request ID.
Expected
A terminal claude_code.llm_request span for the child request, even when canceled mid-stream, with:
- exact
agent_id/ request identity and trace parent; - actual start/end times;
- cancellation/error status and attempt information;
request_id,client_request_id, and response message ID as soon as known.
Alternatively, please document a supported public request-lifetime cancellation receipt for an exact child/provider request. An Agent execution end or stop acknowledgement alone does not identify when the underlying provider client request has ended.
Limits of this report
This requests client-side request-lifetime observability, not a claim that client abort or process exit proves internal server computation physically completed. A provider-side completion/cancellation guarantee would be a separate contract.
The strict request-lifetime assessment failed FOLLOWUP_CHILD_LLM_SPAN_MISSING; it was not converted into a pass. A separate narrower observed-Agent-stop assessment passed identity/timing/closed-admission/parent-continuation/custody checks and 12 negative receipt controls. A later public Options.debugFile diagnostic did not reach a forwarded child block before its bounded timeout and is not counted as a successful canceled-request reproduction.