[BUG] Subagent model-stall watchdog abort (~600s) surfaces as "[Request interrupted by user for tool use]" — 13 transcripts with a tight 600.0–605.6s gap signature

Status Open
Reported on v2.1.206
Maintainer reply None cached
Activity 0 comments · opened Aug 6, 2026

Preflight

Searched existing issues. Related but distinct — cross-linking rather than assuming same bug:

  • #78915 — same exact string, but at dispatch time (subagent never runs). This report is about mid-run aborts of subagents that had been working normally for many turns. I've added our dispatch-time observations as a comment there.
  • #70958 — sub-agent API 401 surfaced as the same string. Same "mis-surfacing sink", different trigger (auth vs. stall/timeout).
  • #39259 — auto-mode safety classifier unavailability. Likely related to the dispatch-time variant (see my comment on #78915), not this mid-run one.

Environment

  • Claude Code 2.1.206 (claude --version), macOS (Darwin 25.x), desktop app; sessions frequently driven remotely from the iOS app — the operator was physically away from the Mac when these fired, so an accidental ESC/Ctrl-C at the keyboard is ruled out.
  • Subagents dispatched via the Agent/Task tool with an explicit model: sonnet pin (all transcripts below ran on claude-sonnet-5).

What's wrong

Long-running subagents die mid-run with the exact string [Request interrupted by user for tool use] recorded as a user-role message in the subagent transcript. The human operator did not interrupt (confirmed by the operator, repeatedly, across several days).

I analyzed every subagent transcript in one project directory (2026-08-02 → 2026-08-04) that contains the string — 14 files. 13 of 14 share a tight machine signature:

  • The last record before the interrupt is a completed tool_result — i.e. the subagent was waiting for its next model response when it died.
  • The gap between that record and the interrupt record clusters in 600.0–605.6 s (exactly 10 minutes + small overhead), across three different days:

| transcript (agent id) | date (UTC) | gap last-record → interrupt |
|---|---|---|
| agent-a91d591fdb2d295cf | 08-02 | 600.029 s |
| agent-ada1c1a2acfef1d02 | 08-02 | 600.067 s |
| agent-a2d68a55ee5d722bf | 08-02 | 602.197 s |
| agent-a12122965632ba5db | 08-03 | 605.018 s |
| agent-a0aae48c7ea3bff15 | 08-03 | 600.166 s |
| agent-a7666cd932335f354 | 08-03 | 603.224 s |
| agent-a0c98b11140c33af4 | 08-03 | 601.101 s |
| agent-a02f9c40996ff6f64 | 08-04 | 605.648 s |
| agent-a9fe407229917db5b | 08-04 | 604.508 s |
| agent-a0b3bd734e9fb5fb6 | 08-04 | 605.410 s |
| agent-ab4c1f66bf1bad6e2 | 08-04 | 604.312 s |
| agent-a95d42dc629f9b88d | 08-04 | 600.032 s |
| agent-a837c0a936a287d6e | 08-04 | 600.014 s |

(The 14th file has a 1476 s gap at line 6 — a different event, excluded.)

Human interrupts would produce arbitrary gaps; a cluster hugging exactly 600 s from below+overhead is a watchdog. Representative record sequence (agent-a837c0a936a287d6e):

2026-08-04T22:42:28.335Z  assistant (claude-sonnet-5)  thinking
2026-08-04T22:42:28.339Z  assistant (claude-sonnet-5)  tool_use
2026-08-04T22:42:28.390Z  user                          tool_result
2026-08-04T22:52:28.404Z  user                          text  "[Request interrupted by user for tool use]"   ← +600.014 s

Consistent with: the subagent's next model request (Sonnet) stalls during a model-availability incident, a ~600 s watchdog aborts it, and the abort is routed through the same path as a user interrupt, so it is recorded and surfaced as one.

Supporting evidence for the availability correlation: during the same incident windows, re-dispatching with the same model: sonnet pin tended to die the same way, while dropping the pin (inherit parent model) or doing the work in the parent succeeded. On non-incident days the identical pinned dispatches run fine.

What should happen

  1. A stalled/timed-out model request inside a subagent should surface as a model/timeout/availability error (like API errors do), not through the user-interrupt path. The parent agent — and the operator reading the transcript — currently misattribute the stoppage to a deliberate human interrupt, which derails the session ("did you cancel this?").
  2. Ideally the timeout would be retryable/named (e.g. model request timed out after 600s), so the parent can decide to re-dispatch on a different model.
  3. Side effect worth noting: subagents killed this way leak whatever they had allocated (in our case: spawned processes, port-lock directories, temp source clones), because they never reach their own cleanup step.

Steps to reproduce

Not deterministic — requires a model-availability incident:

  1. Dispatch a long-running subagent via the Agent tool with an explicit model pin (model: sonnet in our cases).
  2. Let it run for tens of minutes of normal tool use.
  3. If the pinned model degrades, the next model request stalls; ~600 s later the subagent transcript records [Request interrupted by user for tool use] as a user message and the run is dead.

To check whether an occurrence matches this signature, measure the gap between the last record and the interrupt record in ~/.claude/projects/<project>/<sessionId>/subagents/agent-*.jsonl — machine timeout ≈ 600–606 s, human interrupts are arbitrary.

View original on GitHub ↗