Subagent stream dies silently on transient network failure — no retry/reconnect, watchdog kills after 600s of silence
Bug: subagent stream dies silently on transient network failure — no retry/reconnect, watchdog kills after 600s
Environment
- Claude Code: 2.1.233 (also reproduced on earlier 2.1.x builds over the past several days)
- OS: Windows 11 Home 10.0.26200
- Model: claude-fable-5
- Setup: interactive session dispatching multiple parallel subagents via the Agent tool (some with
isolation: worktree), machine awake and actively executing tool calls the whole time (this is NOT the laptop-sleep case of the now-closed #53695)
Symptom
Subagents die with:
Agent stalled: no progress for 600s (stream watchdog did not recover)
15 occurrences in a single session (2026-08-19, ~10:00–14:00 UTC), all in the middle of active work. In every case the subagent transcript (JSONL) shows the identical shape:
- last event: a
tool_resultis delivered to the model (Read/Edit/Bash/Glob results — normal, fast tool calls), or in two cases the session-start attachments (the model never produced its first token), - then exactly 600 s of zero stream events,
- then the watchdog kill (
[Request interrupted by user]injected, task-notification "Agent stalled…").
So the failing step is always the next API streaming request after a completed tool call: it either never connects or dies before the first event, and nothing ever retries it.
Evidence of the underlying transient network failures
The parent session's JSONL for the same time window contains system/api_error records that the main loop logged and recovered from via retry:
10:17:37Z Connection error. "Connection refused — a firewall or proxy may be blocking it (ConnectionRefused)" isNetworkDown: true
11:15:12Z Request timed out.
11:31:58Z Connection error. (ConnectionRefused)
11:47:44Z Connection error. (ConnectionRefused)
12:23:29Z Request timed out.
12:30:33Z Connection error. (ConnectionRefused)
13:37:56Z Request timed out.
Subagent stall timestamps interleave/correlate with these, e.g. a subagent's very first API call stalled at 12:30:02Z — ~30 s around the 12:30:33Z ConnectionRefused in the parent. Stalls also cluster across independent subagents to within seconds, which rules out per-agent behavior and points to a shared network-path fault:
- 10:33:58Z / 10:34:53Z / 10:36:45Z — three different subagents within 3 minutes
- 11:00:16Z / 11:00:27Z — two subagents 11 s apart, both seconds after being dispatched
The same api_error pattern (ConnectionRefused with isNetworkDown: true, request timeouts) appears in this machine's session logs on 2026-08-16/17/18 as well, so the network leg is flaky here — but the parent loop survives every one of these via its retry, while the subagent streaming path never does.
One more live datapoint: during the same window the permission-classifier call also surfaced "model is temporarily unavailable (timed out)" — so multiple independent request paths saw the same transient failures; only the subagent stream path turns them into a 600 s hang followed by a kill.
Expected behavior
The subagent streaming request should get the same treatment as the parent loop's requests: on a transient connection failure (refused / timeout / stream dead before first event), log an api_error and retry/reconnect with backoff instead of sitting silently until the 600 s watchdog fires and kills the agent. "Stream watchdog did not recover" appears to mean there is no recovery path at all for this case — the watchdog only detects, it cannot heal, and detection alone costs 10 minutes plus the kill.
Actual behavior
600 s of silence, then the subagent is killed mid-task.
Related
- #54434 (open) — SSE stream stalls without
message_stopin long-running sessions; same family (stalled stream, no recovery), different trigger (mid-stream stall vs. failed connection before first event). - #53695 (closed) — subagents killed by stream-idle watchdog on laptop sleep. This report is distinct: the machine was awake and executing tool calls throughout.
Mitigation that works today
Resuming the stalled subagent with a follow-up message (SendMessage) recovers it fully — the transcript survives, and in this session 15/15 resumed agents completed their tasks. That confirms nothing is lost except the 10 minutes and the manual resume; an automatic retry at the stream layer would remove the failure mode entirely.