[Bug] Async agent stall watchdog kills healthy long-running requests at 600s timeout
Bug Description
Background subagents are killed at exactly 600s by CLAUDE_ASYNC_AGENT_STALL_TIMEOUT_MS when the model's time-to-first-chunk exceeds it — the work is still alive, and the task is reported "failed".
What happens
The async-agent stall watchdog (CLAUDE_ASYNC_AGENT_STALL_TIMEOUT_MS, default 600000) aborts a background subagent after 600s with no stream chunk. It resets on every chunk and a ~1 Hz progress tick, and defers while a tool is in flight — so it can only fire in the window between a tool_result and the next assistant chunk. On expiry it aborts, writes [Request interrupted by user] into the subagent transcript, and reports the task failed with Agent stalled: no progress for 600s (stream watchdog did not recover).
The problem is that 600s is inside the normal latency distribution for heavyweight reasoning tiers at xhigh effort over 100k+ token contexts. It is not detecting a dead stream; it is guillotining a slow-but-healthy one.
Evidence (claude-code 2.1.224, macOS, 2026-08-09)
Measured across one machine's entire subagent transcript history:
| model | agents | stalls | rate |
|---|---:|---:|---|
| fable | 49 | 5 | 10.2% |
| opus | 158 | 4 | 2.5% |
| sonnet | 114 | 0 | 0% |
| haiku | 4 | 0 | 0% |
- Every stall is at exactly 600.0s after a
tool_result, matching the default. - Recovered near-misses at 560s, 475s and 407s — the latency tail runs right up to the wall, so the cutoff is arbitrary rather than diagnostic.
- Stalls cluster across independent sessions: two different sessions stalled 70 seconds apart on 2026-08-03, with overlapping 600s windows. That is a shared upstream condition, not a per-agent fault.
- Resuming the killed task by id completes it normally — proving the work was viable. (The resumed turn shows
cache_read=0, so the whole context is re-paid.)
Impact
Long planning/analysis subagents on the top tiers fail ~10% of the time and lose all their work unless the user happens to notice and resume. Two of five agents in one session were only recovered manually; three were lost outright.
Suggested fixes
- Scale the default by model tier / effort level, or make it adaptive rather than a flat 600s.
- Auto-resume once on watchdog expiry before declaring the task failed — resume already works and is the documented recovery.
- Surface retry/backoff activity as a progress tick so the timer isn't counting during a live retry.
- Report it as a timeout rather than
failed, and say the task is resumable in the notification itself.
Separate, smaller diagnostic issue
A subagent's .output path is a symlink into <project>/<session>/subagents/agent-<id>.jsonl. ls/stat report the link's size — the target path's length — so every subagent appears frozen at an identical byte count (146 on this machine) no matter how much it writes. This makes the symlink size look like a wedge signature and cost real debugging time; a healthy sonnet agent measures 146 too. Writing the file directly, or documenting that the path is a symlink, would avoid it.
Environment Info
- Platform: darwin
- Terminal: tmux
- Version: 2.1.224
- Feedback ID: 533358a4-89eb-4909-82d6-ce52f77fce95
Errors
[]This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗