[BUG] Subagent transcripts often never receive final cumulative `usage` (main-session transcripts always do) — token accounting from transcripts undercounts subagent output by up to ~2/3
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
In session transcript JSONL files, assistant records are written per content
block with an early snapshot usage (stop_reason: null, output_tokens
often 1), and on request completion a record with the final cumulative usage
(non-null stop_reason, iterations array, server_tool_use) is
written/backfilled.
- Main session files: the final usage is backfilled onto every record of the
requestId. Observed 0 misses across 160+ requests.
- Subagent files (
<session>/subagents/agent-*.jsonland
subagents/workflows/wf_*/agent-*.jsonl): ~20% of requests never get any
final-usage record — every record for the requestId keeps the early
snapshot (stop_reason: null, no iterations), so the request's true output
token count is unrecoverable from the transcript.
Reproduced on 2.1.177 and 2.1.222 (linux x64, native build, VS Code extension
entrypoint and headless -p alike; Agent-tool subagents and workflow subagents
alike). The loss is race-like: it affects normally-completing agents, first or
middle requests of a file (a later request in the same file can be finalized
fine), and sequential and parallel spawns at similar rates.
Impact: where a final record exists, its usage matches harness-side metering
exactly (verified repeatedly against the Workflow budget.spent() API). Where
it is lost, the banked snapshot is near zero: measured examples — a
two-tool-call request recorded 65 of 250 real output tokens; a
StructuredOutput request recorded 4 of ~112; trivial text replies record 1.
Because thinking tokens appear only in the final cumulative usage (and
thinking blocks are written with empty thinking text in subagent files),
reasoning-heavy subagent workloads lose the most: on one real subagent-heavy
corpus, transcripts held only ~1/3 of harness-metered output tokens even
though only ~8% of requests lost their final. Any tool that accounts usage
from transcripts (per-request max of usage.output_tokens) therefore
systematically undercounts subagent work.
What Should Happen?
Subagent transcripts should backfill the final cumulative usage per requestId
the same way main-session transcripts do (or at least flush the final-usage
record before the agent's transcript file is closed). Every completed API
request in a subagent file should end up with at least one record carrying the
final cumulative usage and a non-null stop_reason.
Error Messages/Logs
No errors are emitted — the failure is silent. Example of an affected agent
file: the subagent completed normally and returned its result to the parent,
but its only assistant record still carries the initial stream snapshot:
{"type":"assistant","requestId":"req_011CdjuJFP4DcU9LBfam","message":{"content":[{"type":"text","text":"plum-4"}],"stop_reason":null,"usage":{"input_tokens":2600,"cache_creation_input_tokens":5325,"cache_read_input_tokens":0,"output_tokens":1,"service_tier":"standard"}}}
A healthy sibling agent from the same run, for comparison, gets a finalized
record (`stop_reason` set, `iterations` present, correct `output_tokens`):
{"type":"assistant","requestId":"req_011CdjuJXh5pEP1rN9PM","message":{"content":[{"type":"text","text":"plum-2"}],"stop_reason":"end_turn","usage":{"input_tokens":2600,"cache_read_input_tokens":4231,"cache_creation_input_tokens":1088,"output_tokens":6,"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"iterations":[{"input_tokens":2600,"output_tokens":6,"cache_read_input_tokens":4231,"cache_creation_input_tokens":1088,"type":"message"}]}}}
(Usage payloads abridged to the relevant fields; values are from a real run.)
Steps to Reproduce
mkdir /tmp/subagent-usage-repro && cd /tmp/subagent-usage-repro
claude -p --dangerously-skip-permissions 'Using the Agent tool, spawn 10 subagents IN PARALLEL (all in one message). Subagent N (1..10) gets exactly this prompt: "Reply with exactly the word plum-N. Do not use any tools." When all return, reply with the single word: done'
python3 - <<'EOF'
import json, glob, os, collections
for f in glob.glob(os.path.expanduser(
"~/.claude/projects/-tmp-subagent-usage-repro/*/subagents/agent-*.jsonl")):
reqs = collections.defaultdict(lambda: {"max": 0, "final": False})
for line in open(f):
r = json.loads(line)
if r.get("type") != "assistant": continue
m = r["message"]; u = m.get("usage") or {}
e = reqs[r.get("requestId")]
e["max"] = max(e["max"], u.get("output_tokens") or 0)
e["final"] |= m.get("stop_reason") is not None
for rid, e in reqs.items():
print(os.path.basename(f), rid, "max_output_tokens:", e["max"],
"ok" if e["final"] else "FINAL-USAGE-NEVER-WRITTEN")
EOF
Typical output: 8 agents show max_output_tokens: 6 ok, 2 showmax_output_tokens: 1 FINAL-USAGE-NEVER-WRITTEN. Every agent in the run
completed normally (all ten plum-N replies returned to the parent). Rates
are similar with sequential spawning, tool-using agents, and workflow
subagents; which agents are affected varies run to run.
Claude Model
Other
Is this a regression?
No, this never worked
Last Working Version
_No response_
Claude Code Version
2.1.222
Platform
Anthropic API
Operating System
Ubuntu/Debian Linux
Terminal/Shell
Other
Additional Information
- Environment: Claude Code 2.1.222, linux x64 native build
(Debian 13, kernel 6.12.x), default model (Fable 5). Seen from both the VS
Code extension and headless claude -p.
- Related smaller observation:
thinkingblocks in subagent transcripts are
written with empty thinking text even on requests whose final usage shows
hundreds of thinking tokens (main-session files retain thinking text). If
intentional, fine — but it also removes any fallback for estimating lost
usage from content.
- A killed/interrupted subagent (TaskStop) is NOT the trigger: kill tests
produced clean transcripts, and losses occur in agents that complete
normally and deliver their results.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗