[BUG] Headless stream-json returns after exactly 600s while active background subagents lose completion records
Preflight checklist
- [x] Searched existing open and closed issues. The closest reports are linked below, but none combines the current version, headless stream-json path, verified child activity, and exact 600-second terminal boundary.
- [x] This is a single lifecycle/completion bug.
- [x] Reproduced on the latest release, Claude Code 2.1.220.
What's wrong?
In a non-interactive -p session using bidirectional stream-json, the parent turn emitted a terminal result while two background Agent-tool subagents were still active and had not produced completion records.
Observed twice within one resumed session. Detailed relative timing from the latest occurrence:
t=0: parent output indicated it was waiting for two background subagents.t=+571.119s: subagent A wrote its last valid transcript/tool record.t=+589.379s: subagent B wrote its last valid transcript/tool record.t=+600.503s: the top-level stream emitted its terminalresult.
The caller did not configure a turn timeout and did not send a signal, EOF, or close stdin before receiving that terminal result. It closed stdin only afterward, as part of the normal stream-json turn lifecycle.
On the next invocation with --resume, Claude Code reported that both agents had no completion record from the previous invocation. Their transcripts remained on disk, and both could be resumed manually.
This looks like parent-stream inactivity is being treated as an async-agent stall even though child transcripts are still progressing. The native 2.1.220 binary contains the identifiers CLAUDE_ASYNC_AGENT_STALL_TIMEOUT_MS and tengu_async_agent_stall_timeout; the caller did not set that environment variable. Connecting those identifiers to this event is an inference, but the measured 600.503-second boundary is notable.
What should happen?
While a parent turn owns active background subagents:
- Child transcript/tool activity should count as liveness.
- The parent should not emit a successful/normal terminal result until each owned child has a durable completion state.
- If Claude Code must terminate the parent turn, the result should explicitly identify incomplete agent IDs and report an incomplete/error terminal reason rather than losing their completion records.
- Resuming the session should preserve or correctly re-adopt in-flight children.
Steps to reproduce
- On macOS arm64, start a headless streaming session and keep stdin open:
``shell``
claude -p \
--input-format stream-json \
--output-format stream-json \
--verbose \
--session-id <uuid>
- Send a stream-json user message asking the parent to launch two independent background Agent-tool subagents. Give each a tool-heavy, read-only task expected to run for at least ten minutes, and ask the parent to wait for both results.
- Keep stdin open and consume stdout continuously.
- Verify from the persisted subagent transcripts that child tool activity continues while the parent is otherwise waiting.
- Around ten minutes after the parent begins waiting, observe whether the top-level stream emits a terminal
resultwithout child completion records. - Close stdin after the result, then start a new invocation with
--resume <session-id>. - Observe missing-completion notifications for the previous subagents even though their transcripts exist.
The timing may depend on keeping the parent stream quiet while the children remain active.
Environment
- Claude Code:
2.1.220 (Claude Code) - Release: latest as of 2026-07-27
- Installation: standalone native binary
- OS: macOS 26.5.1, arm64
- Terminal: non-interactive/headless
- API platform: Anthropic first-party
- Model: Fable
- Regression status: unknown
CLAUDE_ASYNC_AGENT_STALL_TIMEOUT_MS: unset
Related reports
- #72171 — nearly identical per-invocation
claude -p --resumeorphan/lost-completion behavior; closed as a duplicate without an apparent fix for this process-boundary case. - #75438 — completion notifications lost after CLI restart; transcripts survive and manual resume is required.
- #75037 — background sessions terminating at approximately ten minutes with lost completion records.
- #75658 — subagents ending without final text, recoverable through manual resume/message.
- #81630 — version 2.1.220 watchdog can kill a parent worker while an in-flight subagent is legitimately quiet, on a different background-session path.
The distinguishing evidence here is that both children had recent persisted activity, no caller timeout or early EOF occurred, and the parent terminal boundary was measured at 600.503 seconds.