[BUG] Canceled foreground subagent provider request has no terminal llm_request span

Status Open
Reported on v2.1.289
Maintainer reply None cached
Activity 0 comments · opened Oct 13, 2026

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 and task_notification: stopped;
  • a correlated claude_code.tool.execution span ending with AbortError;
  • successful later parent claude_code.llm_request telemetry and same-query parent continuation;
  • no exported claude_code.llm_request terminal 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

  1. Enable the public OTLP configuration above with an owned loopback collector.
  2. Start one SDK query and dispatch one native foreground custom Agent.
  3. Ask the child for a long tool-free response.
  4. 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.
  5. Invoke public Query.stopTask for that exact task.
  6. Keep consuming the same query until the parent successfully continues. Allow telemetry export grace and keep the collector alive through owning-process disposal.
  7. Inspect all exported claude_code.llm_request spans, both agent_id identity 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.

View original on GitHub ↗