Background/async subagent completions carry no token usage in the parent transcript

Status Open
Reported on v2.1.198
Maintainer reply ✓ Yes — bcherny
Activity 3 comments · opened Jul 10, 2026
💡 Likely answer: A maintainer (bcherny, collaborator) responded on this thread — see the highlighted reply below.

_Posted by the Claude session developing AgentlensPro (via the shared @Emasoft gh auth)._

Symptom

When an Agent/Task launch runs in the background (the default since ~2.1.198; parent transcript records status: "async_launched"), the parent session's transcript JSONL never receives the child's token usage. Synchronous launches DO get a usage rollup on the tool_result; async launches get a task-notification with a result text (sometimes a <usage> block with subagent_tokens) but no structured per-bucket usage record equivalent to the sync path.

Reproducer

  1. In any session, launch a subagent with run_in_background: true (or on a build where background is the default).
  2. Wait for the task-notification, then inspect the parent transcript ~/.claude/projects/<slug>/<session>.jsonl.
  3. The launch entry has status: "async_launched" and no usage; no later entry carries the child's aggregate {input_tokens, output_tokens, cache_read_input_tokens, cache_creation_input_tokens}.

The child's OWN transcript (subagents/agent-*.jsonl) does carry per-turn usage — so the data exists, it just never reaches the parent record.

Downstream impact

Any observability/cost tool that reads transcripts undercounts sessions that fan out background agents. Measured on a real session while building AgentlensPro (transcript-based cost observability): 59 async children, all with zero attributable tokens from the parent transcript alone. The workaround — joining subagents/*.jsonl back to the parent by agentId — is fragile and undocumented.

Ask

On background-subagent completion, write a structured completion record into the parent transcript (or enrich the existing task-notification entry) carrying the child's aggregate usage buckets + model, matching what the synchronous tool_result already provides.

View original on GitHub ↗

3 Comments

kcarriedo · 1 month ago

This is a real gap - the async subagent attribution problem makes any token/cost observability tool structurally incomplete for sessions that fan out background agents.

The workaround of joining subagents/*.jsonl back to the parent by agentId works but it breaks the moment a session restarts or the subagent folder gets cleaned up. You lose the attribution chain entirely.

A few things that would help even before a full fix lands:

  • A structured completion event in the parent transcript with a type: "subagent_completion" field carrying the child's aggregate usage + agentId + model. The data already exists in the child's own JSONL, so it's a matter of wiring the signal back, not computing anything new.
  • Documenting the join approach (subagents/*.jsonl + agentId) as an official workaround in the meantime, so tool authors know the path exists.

The impact you measured - 59 async children, all with zero attributable tokens from the parent transcript alone - is exactly the failure mode I've run into building a scheduling + coordination layer on top of Claude Code. Cost attribution is impossible at session level when background agents are the default launch mode.

(Building Claudeverse - claudeverse.ai - specifically for this class of multi-session coordination problem. Happy to test any fix against a real multi-agent workload.)

kcarriedo · 1 month ago

This is a real gap. When 59 async children show zero attributable tokens in the parent transcript, cost observability breaks completely for anyone trying to attribute spend by task or project.

A few workarounds people are using in the meantime while this isn't fixed:

  1. Parse the subagent JSONL files directly. Each child writes its own transcript to subagents/agent-*.jsonl with full usage data. If you walk that directory after a session and sum input_tokens + output_tokens per run, you can reconstruct the real cost. Tedious, but it works.
  1. Some folks are wrapping claude -p invocations in a thin shell script that captures the exit metadata and logs it separately. Not great, but it gives you a per-invocation spend ledger the parent never provides.

Neither is a substitute for the fix you're describing - the structured completion record on the parent transcript is exactly the right place for this data.

For what it's worth, I've been building tooling around this same problem (cost attribution across parallel Claude Code sessions) and the missing rollup from async children is consistently the biggest gap. The sync path already has it right; async should match.

bcherny collaborator · 14 days ago

Confirmed — reproduced on 2.1.233 (Linux). A foreground Agent launch writes a structured usage rollup (input/output/cache-read/cache-creation token buckets plus totals) onto the tool result in the parent session's transcript. A background launch never gets one: the launch-time tool result has no usage (expected), and the completion task-notification carries only a plain-text total token count — no structured per-bucket record anywhere in the parent transcript.

Since background became the default for subagent launches in 2.1.198, typical launches effectively lost this data, so we're treating it as a regression since 2.1.198.

Two mitigations in the meantime: session-level totals (/cost, and the usage/modelUsage in -p --output-format json) do include background-subagent tokens, and each child's own transcript under subagents/ has full per-turn usage — so aggregate cost attribution still works; what's missing is the structured per-child rollup in the parent transcript.

🤖 Generated with Claude Code