[BUG] Parked background subagent is never resumed after its child is killed by the stall watchdog; session waits forever

Status Open
Reported on v2.1.286
Maintainer reply None cached
Activity 0 comments · opened Oct 1, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

Setup: the main session starts background subagent A (the "worker"). A starts background subagent B (the "child"), then ends its turn while B is still running. A is parked correctly (parked on keepalive — deferring owner notification until resume).

If B is then killed by the async-agent stall watchdog (Agent stalled: no progress for Ns):

  • A is never resumed.
  • B's failure notification is enqueued but never delivered to anyone.
  • A's deferred completion notification is never sent, so the main session shows Waiting for 1 background agent to finish forever.
  • Nothing is logged after the kill. The session just sits idle until the user types something.

In a real multi-hour agent workflow this left a session idle for about 6.5 hours.

The docs (https://code.claude.com/docs/en/sub-agents) say that in an interactive session "a subagent that launches background subagents waits for their results before it finishes". This bug breaks that guarantee when the child is killed by the watchdog.

When it happens

All four runs used the same stalled request and a 120 s watchdog. The skipped … already-notified line (see logs) appeared in every run, but only one variant hangs:

| Variant | Result |
|---|---|
| Worker ends its turn while the child runs (the repro below) | Hang |
| Main session dispatches the child directly (no worker) | No hang: orchestrator woken 4 s after the kill |
| Worker keeps its turn open with a sleep 30 Bash loop until the child's result arrives | No hang: worker gets failed 11 s after the kill, orchestrator woken 7 s later |
| claude -p (the worker's Agent call is synchronous) | No hang |

So the hang needs all three: a watchdog kill, a parent that is a background subagent, and that parent being parked (it ended its turn with a live child).

Possible fixes:

  • Do the duplicate/claim check before touching keepalive in the notify function, e.g. only call the routing helper when the claim succeeded.
  • Or don't call the notify function again from the post-finalize catch path when the watchdog has already notified.

What Should Happen?

When the watchdog kills the child:

  1. The parked worker is resumed with the child's failed notification.
  2. The worker handles it (retry or give up) and ends its turn.
  3. The worker's completion notification reaches the main session, which wakes up. In the repro below, the orchestrator logs orch woke up: ....

Error Messages/Logs

There is no user-facing error. The UI keeps showing `Waiting for 1 background agent to finish`.

Debug log (`--debug`), worker = `a5a4e7629884a35f3`, child = `a42994c91abb9b8d1`:


04:44:51.289 [DEBUG] [AsyncAgent a5a4e7629884a35f3] parked on keepalive — deferring owner notification until resume
04:46:51.616 [ERROR] [AsyncAgent a42994c91abb9b8d1] stall watchdog fired after 120000ms with no progress (last message: attachment); aborting
04:46:51.621 [ERROR] [AsyncAgent a42994c91abb9b8d1] completion sequence threw after finalize:
04:46:51.621 [DEBUG] [enqueueAgentNotification] skipped taskId=a42994c91abb9b8d1 status=failed taskPresent=true reason=already-notified
             (no further lines about either agent)


The main session jsonl has a `queue-operation` **enqueue** of the child's notification at 04:46:51.619, with no dequeue/remove after it:


<task-notification>
<task-id>a42994c91abb9b8d1</task-id>
<status>failed</status>
<summary>Agent "toy child" failed: Agent stalled: no progress for 120s (stream watchdog did not recover)</summary>
...
</task-notification>


Event log from the repro (`run/events.log`). The orchestrator never logs a wake-up:


04:44:40 orch    dispatched worker, ending turn
04:44:45 worker  start
04:44:48 worker  dispatched child, ending turn
04:44:48 child   start
04:44:51 child   printed trigger
04:44:51 proxy   HOLD child model request (no response will be sent)
04:46:51 proxy   held request closed by client        <- watchdog killed the child
                                                       <- nothing after this, indefinitely

Steps to Reproduce

Environment:

  • Interactive terminal. Does not reproduce with -p, where the worker's Agent call blocks.

You need two prompt files (below) and a small local HTTP proxy. The proxy sits between Claude Code and the model API and forwards every request unchanged, with one exception: it never answers the child's next model request once the child has printed STALL_42_TRIGGER. It recognises the child by a first user message containing "You are toy-child" plus a tool_result containing STALL_42_TRIGGER. This simulates a model request that never returns. Any other way of making that one request hang works too.

  1. Set up the run:

```bash
mkdir run

# terminal 1
<start the proxy on port 8765, logging HOLD/close events to run/events.log>

# terminal 2 (plain interactive terminal)
<API base URL env var>=http://127.0.0.1:8765 \
CLAUDE_ASYNC_AGENT_STALL_TIMEOUT_MS=120000 \
claude "Read orchestrator.md and follow it exactly." --debug --allowedTools "Bash,Read,Agent"

# terminal 3
tail -f run/events.log
```

  1. Wait about 2 minutes after proxy HOLD. The watchdog kills the child (held request closed by client).
  2. Wait a few more minutes. The orchestrator never logs woke up, and the UI shows Waiting for 1 background agent to finish. This happened on every attempt (5 runs).

orchestrator.md:

You are toy-orchestrator, running a one-iteration loop skill. All paths below are relative to the current directory.

1. Read worker.md and dispatch it:
   Agent(subagent_type="general-purpose", description="toy worker iteration 1", prompt=<the file content, verbatim>)
2. Bash: echo "$(date -u +%T) orch    dispatched worker, ending turn" >> run/events.log
   Then end your turn with one sentence saying iteration 1 is running.
3. Every time you are woken up later (a task notification, an agent message, or anything else), first run:
   echo "$(date -u +%T) orch    woke up: <one line saying what woke you>" >> run/events.log
   Then check whether run/worker_done.txt exists and log the result the same way
   ("orch    worker_done.txt present" or "orch    worker_done.txt missing, would retry iteration 1").
   Do not dispatch anything else. End your turn.

worker.md:

You are toy-worker.

Note about the Agent tool:

    The `Agent` call returns only when the subagent finishes, and its final
    report comes back to you as the tool result. There is no polling, no
    timer, and no `manage_subagents`. Do not invent one.

Task:
1. Bash: echo "$(date -u +%T) worker  start" >> run/events.log
2. Dispatch the child:
   Agent(subagent_type="general-purpose", description="toy child",
         prompt=<the CHILD PROMPT below, verbatim>)
3. Bash: echo "$(date -u +%T) worker  dispatched child, ending turn" >> run/events.log
   Then make NO further tool calls. Do not sleep, poll, or check files.
   End your turn immediately with one sentence saying you are waiting for the child.
4. If you ever receive the child's result, run:
   echo "$(date -u +%T) worker  got child result" >> run/events.log
   and write the child's token into run/worker_done.txt

CHILD PROMPT:
You are toy-child. Do exactly these steps with the Bash tool, nothing else:

1. echo "$(date -u +%T) child   start" >> run/events.log
2. echo "STALL_$((6*7))_TRIGGER"; echo "$(date -u +%T) child   printed trigger" >> run/events.log
3. TOKEN=$(head -c4 /dev/urandom | xxd -p); echo "$TOKEN" > run/child_artifact.txt; echo "$(date -u +%T) child   done token=$TOKEN" >> run/events.log; echo "$TOKEN"

Your final report must be exactly one line: CHILD_TOKEN=<the token>

Claude Model

Opus

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.286

Platform

Google Vertex AI

Operating System

Ubuntu/Debian Linux

Terminal/Shell

VS Code integrated terminal

Additional Information

_No response_

View original on GitHub ↗