SubagentStop does not fire when a background subagent is killed via TaskStop or by "Exit and stop tasks"

Status Open
Reported on v2.1.261
Maintainer reply None cached
Activity 0 comments · opened Sep 7, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report
  • [x] I am using the latest version of Claude Code

What's Wrong?

A SubagentStop command hook fires when a background subagent completes on its own. It does not fire when the parent session kills the subagent with TaskStop, and it does not fire when the session exits through the "Exit and stop tasks" choice in the /exit dialog. Both paths leave a SubagentStart without a matching SubagentStop.

I reproduced this in headless mode (claude -p) and in the interactive REPL. In the same sessions, a subagent that finished normally produced a SubagentStop with last_assistant_message, so the hook is registered and working. The killed agent is removed from the task registry, because the next main-thread Stop payload no longer lists it in background_tasks. Only the hook is missing.

Nothing about the missing hook appears in the debug log written with --debug-file. The log has no line containing SubagentStop for the killed agent.

What Should Happen?

Every SubagentStart is followed by exactly one SubagentStop, regardless of how the subagent ends. A kill through TaskStop or through session exit is expected to fire the hook, with last_assistant_message empty if there is none.

Error Messages/Logs

Hook log from one interactive session. Each line is one hook invocation, written by the jq command from the settings below. Times are seconds since the first line.

+0    {"ev":"SubagentStart","agent_id":"a7e17cab67db81d3b","agent_type":"general-purpose"}
+18   {"ev":"Stop","agent_id":null,"bg":[]}                     <- main turn after TaskStop, agent already gone
+21   {"ev":"Stop","agent_id":null,"bg":[]}                     <- woken turn after the "was stopped" notification
      (no SubagentStop for a7e17cab67db81d3b, checked again 75 s later)

+147  {"ev":"SubagentStart","agent_id":"a407d56a2f914aea2","agent_type":"general-purpose"}
+148  {"ev":"Stop","agent_id":null,"bg":[{"id":"a407d56a2f914aea2","type":"subagent","status":"running",...}]}
+240  {"ev":"SubagentStop","agent_id":"a407d56a2f914aea2","agent_type":"general-purpose","last":"done",...}
+243  {"ev":"Stop","agent_id":null,"bg":[]}                     <- control: normal completion fires the hook

+267  {"ev":"SubagentStart","agent_id":"aeb8c19d42bf9982c","agent_type":"general-purpose"}
+268  {"ev":"Stop","agent_id":null,"bg":[{"id":"aeb8c19d42bf9982c","type":"subagent","status":"running",...}]}
      (/exit, "Exit and stop tasks" -> session ends, no SubagentStop for aeb8c19d42bf9982c)

Two headless runs (claude -p) gave the same result for the TaskStop path, with the process kept alive for 20 s after the kill.

Steps to Reproduce

  1. Save this overlay settings file as /tmp/subagent-stop/settings.json:

``json
{
"hooks": {
"SubagentStart": [
{
"hooks": [
{
"type": "command",
"command": "jq -c '{ev:.hook_event_name,agent_id,agent_type}' >> /tmp/subagent-stop/hooks.log"
}
]
}
],
"SubagentStop": [
{
"hooks": [
{
"type": "command",
"command": "jq -c '{ev:.hook_event_name,agent_id,agent_type,last:.last_assistant_message,bg:.background_tasks}' >> /tmp/subagent-stop/hooks.log"
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "jq -c '{ev:.hook_event_name,agent_id,bg:.background_tasks}' >> /tmp/subagent-stop/hooks.log"
}
]
}
]
}
}
``

  1. Save this prompt as /tmp/subagent-stop/prompt.txt:

``
Follow these steps exactly and do nothing else. Step 1: call the Agent tool with subagent_type "general-purpose" and the prompt: Run the Bash command sleep 90 and then reply with the single word done. Step 2: run the Bash command sleep 15. Step 3: call TaskStop with the task id that the Agent tool returned. Step 4: run the Bash command sleep 20. Step 5: reply with the single word KILLED.
``

  1. Run headless:

``
cd /tmp/subagent-stop
claude -p --settings ./settings.json --allowedTools Agent TaskStop 'Bash(sleep:*)' < prompt.txt
cat hooks.log
``

  1. Observe one SubagentStart and one Stop, and no SubagentStop.
  1. Control: change the subagent prompt to "Reply with the single word done" and drop steps 2 to 4. SubagentStop fires.
  1. Interactive variant: start claude --settings ./settings.json --allowedTools Agent TaskStop 'Bash(sleep:*)' in the same directory and paste the prompt. Same result. Then launch another sleeping agent, run /exit, and pick "Exit and stop tasks". No SubagentStop for that agent either.

Claude Model

Haiku 4.5 (claude-haiku-4-5-20251001) for both the parent and the subagent. The parent model does not matter for the hook path.

Is this a regression?

Unknown. I have not tested an older version.

Claude Code Version

2.1.261 (Claude Code)

Platform

Anthropic API (claude.ai login)

Operating System

macOS 26.6

Terminal/Shell

tmux (interactive runs), zsh (headless runs)

Additional Context

  • #78463 reports missing SubagentStop for subagents that die during an API-error burst. #44971 reported the same for team agents shut down through the shutdown protocol. This report is the same class with a deterministic trigger: TaskStop and session exit.
  • This differs from #82249. On 2.1.261, SubagentStop does fire for background agents that complete normally, as the control run above shows.
  • Impact: any hook that pairs SubagentStart with SubagentStop to track in-flight agents drifts after every kill. The background_tasks field of the Stop payload is a workaround for the parent session, but it does not help hooks that need the per-agent stop event.

View original on GitHub ↗