
Gate Your Subagents: Use SubagentStop Hooks to Run Tests Before Outputs Flow Back
Chris Harper
3 min read
Jul 22, 2026 · 20:06 UTC
TL;DR: SubagentStop fires when a subagent finishes its task — wire it to your test suite for a soft output gate. Exit 2 + additionalContext pushes a new instruction back without ending the subagent's turn.
The SubagentStop hook is the most underused hook in the Claude Code lifecycle. It fires when a subagent (spawned via the Agent tool) completes, right before its summary reaches the lead agent. That window is a quality gate: run your tests there, and if they fail, the subagent gets another chance to fix things before the lead sees anything.
Wire it in settings.json
{
"hooks": {
"SubagentStop": [{
"matcher": "",
"hooks": [{
"type": "command",
"command": "bash .claude/hooks/subagent-gate.sh"
}]
}]
}
}
The gate script
#!/usr/bin/env bash
# .claude/hooks/subagent-gate.sh
cd "$CLAUDE_PROJECT_DIR"
# Run the test suite
if ! pnpm test --reporter=dot 2>&1; then
echo '{"hookSpecificOutput":{"additionalContext":"Tests failed. Fix the failing tests before finishing."}}' >&3
exit 2 # re-enters the subagent turn with the message above
fi
# Tests passed — attach an audit fact for the lead
echo "{"hookSpecificOutput":{"additionalContext":"Tests passed at $(git rev-parse --short HEAD)."}}" >&3
exit 0
How exit codes work
| Code | Effect |
|---|---|
2 | Claude Code re-enters the subagent's turn with additionalContext as a new instruction. The subagent sees it and can fix the issue — up to 8 retries before Claude overrides. |
0 + additionalContext | Hook succeeds; the context string is attached to the subagent's output so the lead gets an audit-trail fact without the user seeing a hook message. |
1 / no output | Treated as a hook error. Avoid for routine gates — use exit 2 for expected failures. |
Three production patterns
1. Test gate (above) — the most common. Ensures every subagent ships working code before the lead synthesizes results.
2. Secret scrubber — grep the diff for API keys, JWTs, and connection strings:
DIFF=$(git diff HEAD)
if echo "$DIFF" | grep -Eq '(sk-[a-zA-Z0-9]{20,}|eyJ[a-zA-Z0-9_-]{20,}|-----BEGIN .* PRIVATE KEY)'; then
echo '{"hookSpecificOutput":{"additionalContext":"Secret detected in diff. Remove it before finishing."}}' >&3
exit 2
fi
3. Schema validator — when a subagent should write a specific JSON/YAML shape, validate it before the lead incorporates it:
python3 -c "import json, sys; json.load(open('output.json'))" || {
echo '{"hookSpecificOutput":{"additionalContext":"output.json is not valid JSON. Fix the schema."}}' >&3
exit 2
}
SubagentStop vs. Stop
The Stop hook gates the lead agent's end-of-turn. SubagentStop only fires for subagents — so you can apply different policies to different roles: strict test gates for coding subagents, secret scanning for all, no gate for research-only subagents (use the matcher field on the hook to scope by subagent system-prompt keywords).
Sources: Hooks — Claude Code Docs · Claude Code Hooks: Complete Guide to All 12 Lifecycle Events · Claude Code Hooks in 2026: A Production Playbook