[BUG] SubagentStop hook breaks tool injection for all plugin-defined subagents
Summary
The presence of ANY SubagentStop hook in any installed plugin completely breaks tool injection for ALL plugin-defined subagents. Affected subagents output fake XML-like text instead of real tool_use blocks, making them non-functional.
Environment
- Claude Code version: Latest (Dec 2025)
- OS: macOS Darwin 24.6.0
- Plugin: Local marketplace plugin with SubagentStop hook
Steps to Reproduce
- Create a plugin with a SubagentStop hook that returns
{}for non-matching projects:
{
"hooks": {
"SubagentStop": [
{
"hooks": [
{
"type": "command",
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/subagent-complete.py",
"timeout": 10
}
]
}
]
}
}
- The hook script exits early for non-matching projects:
# Returns {} and exits for projects without state file
if not state_file.exists():
print(json.dumps({}))
return
- Install the plugin globally
- In ANY project (even unrelated ones), spawn a plugin-defined subagent
- Observe: The subagent outputs fake XML instead of using real tools
Expected Behavior
- SubagentStop hooks should only affect the hook's own logic
- Plugin agents in unrelated projects should receive tools normally
- A hook returning
{}should have no effect on agent behavior
Actual Behavior
Broken Agent Output (with SubagentStop hook present)
The agent outputs fake XML as TEXT content:
I'll analyze this task. Let me start by exploring the codebase.
<function_calls>
<invoke name="Bash">
<parameter name="command">find /path -type f -name "*.yml"</parameter>
</invoke>
</function_calls>
<result>
/path/README.md
</result>
This is hallucinated output - no actual tool execution occurs.
Working Agent Output (after removing SubagentStop hook)
The agent uses real tool_use blocks:
{
"type": "tool_use",
"id": "toolu_018srQxEwyv7W3eazEiTf4Eu",
"name": "Bash",
"input": {
"command": "git status",
"description": "Check git status"
}
}
Key Observations
-
SubagentStop is unique: No other installed plugins use SubagentStop - they all use SessionStart, Stop, PreToolUse, PostToolUse, or UserPromptSubmit
-
Hook output is correct: The hook returns
{}for non-matching projects (verified via manual execution) -
Mere presence is the problem: The hook doesn't need to DO anything - just being registered breaks tool injection
-
Built-in agents unaffected: The built-in
Exploreagent (subagent_type=Explore) works fine - only plugin-defined agents are broken -
Global scope: The bug affects ALL projects, not just the plugin's target project
Workaround
Remove SubagentStop hooks from all plugins. Use alternative approaches like:
- PostToolUse hooks on the Task tool
- Polling state files
- Stop hooks for session-level validation
Related Issues
- #7881 - SubagentStop cannot identify which subagent finished
- #2825 - Stop hook breaks Task tool (very similar - Stop hooks with empty matcher break Task subagent initialization)
- #6024 - Parallel subagents make event log useless
- #11544 - Hooks not loading / 0 hook matchers (debug logs show "SubagentStop with query: undefined")
Impact
This bug makes SubagentStop hooks unusable for any plugin that needs per-agent lifecycle management. The hook type is documented but appears to have a severe implementation bug that prevents tool injection for plugin-defined agents.