Idle background session stops advancing while async subagents are in flight, and resumes only when the operator opens it
Summary
A local background session (agents view / claude agents) stops executing entirely for 40–48 minutes. In both cases the stall begins within seconds of the main agent ending its turn while async Agent subagents are still in flight. The whole worker is parked: no timers run, no streams advance, and the in-process subagents stop too.
The session resumes only when the operator opens it again. It does not wake itself when its subagents finish. Their completion notifications are enqueued at the moment of the thaw, not when they were produced.
By then the 600-second subagent stall watchdog has expired. Some subagents are terminated with the literal text [Request interrupted by user], although no user interrupted anything.
This is not the documented idle reap. The daemon never touched the job — ~/.claude/daemon.log contains no line naming it. It is also not a quiet machine: 22 other sessions ran normally during one of the freezes.
The practical effect is that a background session with a fan-out of subagents makes no progress unless a human sits and watches it, which defeats the point of running it in the background.
I have two events from one night with full timestamps, six freezes across five sessions in one day, and I have excluded the obvious causes with evidence. Details below.
Environment
| | |
|---|---|
| Claude Code | 2.1.261 (also observed under 2.1.260) |
| Platform | macOS, Darwin 25.6.0, Mac mini, 48 GB RAM |
| Mode | local background sessions dispatched from the agents view |
| Scale | 8–11 concurrent background sessions |
| Daemon | origin=transient |
| Remote Control | enabled, so every job carries a bridgeSessionId |
What happens
Both events follow the same shape:
- The main agent dispatches nine async subagents with the
Agenttool. - The main agent ends its turn. The session goes idle with all nine subagents still running.
- Within seconds, everything stops.
- Nothing advances until the operator opens the session again — forty-odd minutes later. Everything then resumes in the same millisecond.
- The subagents are reported as stalled at the 600-second watchdog. Some are killed.
Event 1 — 48 minutes frozen, 6 of 9 subagents stalled, all recovered
| Time (UTC) | Time (PDT) | What |
|---|---|---|
| 2026-09-05T00:30:40–00:30:51 | 17:30:40 | 9 subagents dispatched |
| 2026-09-05T00:31:01 | 17:31:01 | main turn ends, stop_reason: end_turn |
| 2026-09-05T00:31:50–00:32:28 | 17:31:50 | each subagent writes its last record, ragged across 38 s |
| 2026-09-05T01:19:40 | 18:19:40 | thaw |
At the thaw the main agent emitted, verbatim:
API Error: The response stopped arriving. The response above may be incomplete.
with stop_reason: stop_sequence. Five queued task-notification records flushed in the same millisecond. Nine seconds later the main agent reported:
... stalled at the 600-second watchdog with no output. That is an infrastructure failure ...
It resumed all six from their transcripts and they completed. No findings were lost, but only because the agent noticed and recovered by hand.
Event 2 — 40 minutes frozen, 4 of 9 subagents killed permanently
| Time (UTC) | Time (PDT) | What |
|---|---|---|
| 2026-09-05T02:41:04–02:41:20 | 19:41:04 | 9 subagents dispatched |
| 2026-09-05T02:41:35–02:41:39 | 19:41:35 | main turn ends |
| 2026-09-05T02:46:14–02:47:39 | 19:46:14 | each subagent writes its last record, ragged across 85 s |
| 2026-09-05T03:21:50.62 | 20:21:50 | thaw |
At the thaw, four task-notifications enqueued and, in the same millisecond, four subagents received the literal text [Request interrupted by user] and died. Five resumed and finished.
No user interrupted anything. Across both events that string appears in ten subagent transcripts — six under event 1's session, four under event 2's — and never in a main transcript. The same abort was recoverable in event 1 and fatal in event 2. That asymmetry is the clearest evidence that the abort is spurious rather than a real cancellation.
Two of the review facets the panel was dispatched to cover received no coverage at all as a result.
Why this is a process freeze, not a network stall
The signature is ragged stop, millisecond-synchronized resume.
Each subagent stops at whatever moment it last flushed — a 38-second spread in event 1, an 85-second spread in event 2. All of them resume at exactly 01:19:40Z and 03:21:50Z. Ten independent HTTP streams cannot resume in the same millisecond. One suspended process can.
The decisive detail is the timer. The response stopped arriving error fired at the thaw instant, 47 minutes after the request began — not on its own schedule. A live process with a running stream-idle timer would have errored minutes in. The timer was not running. Likewise, the four aborts land in the same millisecond as the notification flush, not spread out.
Both freezes begin within seconds of the main turn ending. Each subagent then writes its last record 50–90 seconds later and stops. Because subagents run in-process, parking the worker parks them too — which is exactly what the ragged-stop/synchronized-resume signature shows.
The freeze ends when the operator returns to the session
This is not a case of the machine being idle or the operator being away.
- Other sessions ran normally throughout. During event 2's freeze, 22 sessions produced records on this host, including four unattended background sessions writing 300+ records each. Only the frozen one stopped.
- The operator was at the keyboard for the whole of event 1, typing five messages between 00:46:21Z and 01:16:03Z — all into a different session. The frozen session stayed frozen while they worked elsewhere, and thawed at 01:19:40Z, minutes after that last message.
- A second frozen session thawed 40 seconds later.
645f1c84had been stalled 58.9 minutes and resumed at 01:20:20Z. Two stalled sessions waking 40 seconds apart is what stepping through the agents view looks like. - No user prompt appears at either thaw. The operator did not type into either session to wake it. The first typed message into the event 1 session came 31 minutes after its thaw.
So an idle background session with async subagents in flight appears not to advance on its own. Its subagent completions are enqueued only when a human opens the session. The very first records at each thaw are queue-operation: enqueue entries for the pending task notifications — the parent had not received them until that moment.
I cannot prove the attach is the trigger, because attaching leaves no transcript record. What I can show is that the thaw is not spontaneous, is not machine-wide, and follows operator interaction. I also cannot fully characterize the trigger condition: other background sessions with in-flight subagents did not freeze the same night.
This is not isolated
Six freezes of 25 minutes or longer across five different sessions on 2026-09-05 alone: 40.2, 42.3, 48.7, 58.9, 89.9 and 101.9 minutes.
What I ruled out, with evidence
| Cause | Evidence against |
|---|---|
| Idle reap / retire | ~/.claude/daemon.log has no line naming this job at all, and nothing between 14:16:33 and 18:20:22 PDT. A retire also settles done and logs bg retire <id>: <cause>, idle <N>. |
| Explicit stop | No bg settled <id> (killed) for this job. No daemon shutdown logged since 2026-07-01. |
| Machine sleep | pmset -g log shows no Sleep/Wake on 2026-09-04. sleep is 0 on AC. caffeinate assertions held throughout. |
| Auto-update | All four transcripts are 2.1.261. The daemon's last self-restart was 12:59:59 PDT, ~4.5 h before event 1, and logged bg adopt: adopted=5 respawned=0 dead=0. |
| Rate limiting | quota-timeseries.jsonl: the 5 h window read 59–64% at event 1's onset and reset to 0% at 18:00 PDT, mid-freeze — the freeze then continued another 20 minutes. Event 2's onset read 17–21%. Neither event was near a limit. |
| Memory pressure | vm.swapusage total = 0.00M. macOS memorystatus_vm_pressure_level = 1 (normal); the low-memory shed needs critical. Zero low-memory lines in 457 KB of daemon log. |
| API-wide outage | Other Claude Code sessions on the same host logged 120–500 records during event 1's window and 166–360 during event 2's. Only this job froze. |
The three bugs
1. The freeze itself. A background worker with in-flight async subagents stops executing for tens of minutes after its main turn ends. This is the root cause.
2. The false interrupt. Whatever the freeze's cause, [Request interrupted by user] is the wrong text and the wrong outcome. No user interrupted. The subagents' own context was intact — the main agent proved this in event 1 by resuming all six successfully. In event 2 four were discarded instead.
The second bug is what converts a recoverable stall into lost work. Event 1 and event 2 had the same freeze and opposite outcomes.
3. Stale in-flight accounting after the session settles. The same job finished at 20:40:05 PDT and wrote state: "done", tempo: "idle". Its state.json still reads:
"inFlight": {"tasks": 2, "queued": 0, "kinds": ["local_bash"], "drainableMonitors": 0}
No matching process exists. The counter never decremented. In the agents view this surfaces as subagents that stay active after they have already returned their result, which I saw on several sessions the same night.
This matters beyond cosmetics: inFlight.tasks > 0 and inFlight.queued > 0 are retire blockers. local_bash appears to be exempt, so this particular leak looks harmless — but a leaked local_agent or a leaked queued count would pin a finished session open permanently, because queued > 0 blocks unconditionally. I also observed a live job holding {"tasks": 0, "queued": 1, "kinds": []} — a queued count with no kind naming it.
Suggested fixes
- Wake an idle background session when one of its async subagents completes. Do not hold the completion until a human opens the session.
- Do not park a background worker while async subagents are in flight.
- Do not count parked wall-clock time against the subagent stall watchdog. On resume, restart the clock rather than expiring it retroactively.
- When a subagent is aborted by a watchdog, say so.
[Request interrupted by user]is false and misleading when no user acted. - Make the stall watchdog's expiry recoverable by default, the way event 1's manual resume was.
- Log the park and the resume. Nothing in
~/.claude/daemon.logrecords a 48-minute gap in a live worker. - Decrement
inFlightwhen a task settles, and reconcile it when a session settles.
Severity, and why there is no workaround
The freeze is the harm. A background session stops doing the work it was dispatched to do, for as long as it takes the operator to notice and open it. Everything else in this report is downstream of that.
There is no local mitigation. I looked:
- Raising
CLAUDE_ASYNC_AGENT_STALL_TIMEOUT_MSis not a fix. It does not make a parked session run. It only stops the watchdog from retroactively killing subagents on resume. The lost wall-clock time remains. There is also no defensible value: the park lasts until a human next opens the session, which is unbounded — overnight, that is eight hours. Setting the threshold that high disables genuine stall detection for subagents that really are hung. - Pinning (
Ctrl+T) does not apply. Pinning blocks the daemon's idle retire. The daemon is not involved here — it logged nothing for these jobs. - The only thing that appears to prevent the park is having the session open, which is the opposite of a background session. During event 1's freeze the session the operator actually had open ran normally, producing 186 records, while the unattended one produced none.
That last point is what makes this scale badly. An operator running 8–11 concurrent background sessions can have one open at a time. Every other session is a candidate to park, and the more work runs in parallel, the smaller the share of it that advances.
What I can supply
Transcript paths, the daemon log, quota-timeseries.jsonl, and pmset output, on request.