Opus subagents ignore shutdown_request while continuing to emit idle_notification (sonnet agents approve normally)
Summary
Subagents spawned via the Agent tool ignored shutdown_request (sent via SendMessage) while continuing to emit idle_notification messages. In one session, 6 shutdown requests produced 2 approvals — and the 4 unacknowledged requests were both sent to the two agents running model: opus, while both model: sonnet agents in the same session approved normally on the first request.
Because each unacknowledged agent keeps emitting idle_notification messages, and each notification is delivered to the parent wrapped in a multi-hundred-token preamble and wakes the parent for a turn, a stuck agent imposes an ongoing token cost until it is force-stopped.
Environment
- Claude Code CLI, macOS (Darwin 25.5.0), zsh
- Parent session model: Opus
task_typereported byTaskStop:in_process_teammatebackendTypereported in approval messages:tmux
Steps to reproduce
- Spawn 4 background subagents in a single message via
Agent, withmodelset explicitly — 2 withmodel: "opus", 2 withmodel: "sonnet". (In my case each was given a distinct set of files to edit; the work itself completed successfully in all 4.) - Let all 4 finish their work and go idle. Each emits
{"type":"idle_notification","idleReason":"available"}. - Send each one
{"type": "shutdown_request", "reason": "..."}viaSendMessage. - Observe which agents return
shutdown_approved.
Expected
All 4 agents return {"type":"shutdown_approved","requestId":"..."} and terminate, as documented in the SendMessage tool description.
Actual
- The 2
sonnetagents each returnedshutdown_approvedwithin ~1s and terminated cleanly (teammate_terminatedreceived for both). - The 2
opusagents returned noshutdown_approved. Instead each emitted anotheridle_notificationat roughly the same timestamp as the shutdown request. - I re-sent
shutdown_requestto both non-responders. Both again produced onlyidle_notificationmessages — 3 more across the two agents — and neither acknowledged. TaskStop(passing the bare agent name astask_id) succeeded on the first attempt for both, returningSuccessfully stopped task: <id>withtask_type: in_process_teammate.
So the agents were alive and responsive enough to emit notifications, but never processed the shutdown_request control message. Notably, these same 2 agents had also completed their work without sending a final report message, while the 2 sonnet agents both reported normally — so the failure to handle protocol/outbound messaging appears correlated across both directions, not specific to shutdown.
Timeline (times UTC, one session)
| Time | Event |
|---|---|
| 23:22–23:26 | All 4 agents complete work; 2 report results, 2 report nothing |
| ~23:46:13 | shutdown_request sent to all 4 |
| 23:46:17 | agent C (sonnet) → shutdown_approved, terminated |
| 23:46:17 | agent D (sonnet) → shutdown_approved, terminated |
| 23:46:17 | agent A (opus) → idle_notification (no approval) |
| 23:46:24 | agent B (opus) → idle_notification (no approval) |
| ~23:46:29 | shutdown_request re-sent to agents A and B |
| 23:46:33 | agent A → idle_notification (no approval) |
| 23:46:36 | agent B → idle_notification (no approval) |
| ~23:46:45 | TaskStop on agent A → success, first attempt |
| ~23:46:47 | TaskStop on agent B → success, first attempt |
Impact
- Token cost. Every
idle_notificationis delivered to the parent wrapped in a standing preamble and triggers a parent turn (prefill of full context + output). An agent that ignores shutdown but keeps emitting notifications bills the parent indefinitely. This is small per event but unbounded in duration, and it is pure overhead — the agent's work was already complete. - No signal that the handshake failed.
SendMessagereturns{"success":true, ...}for ashutdown_requestthat is never processed. From the parent's side a dropped request is indistinguishable from one in flight, so the only way to notice is to observe that noshutdown_approvedever arrives. - Misleading guidance. The
SendMessagedocs frameshutdown_requestas the way to terminate a teammate and caution against originating one unless asked. There is no documented fallback for a non-responsive agent —TaskStopturns out to be strictly more reliable and needs no cooperation, but nothing points you there.
Suggested fixes
- Make
shutdown_requesthandling not depend on the agent's model or its inference loop — terminate at the harness level after a grace period. - Have
SendMessagesurface a timeout/nack for an unacknowledgedshutdown_requestrather than a baresuccess: true. - Suppress
idle_notificationfrom an agent with a pending shutdown request; a shutting-down agent has nothing useful to report. - Document
TaskStopas the fallback for a non-responsive teammate.
Workaround
Use TaskStop with the bare agent name instead of the shutdown_request handshake. It worked first try on both stuck agents and does not require the agent to cooperate.