PreToolUse/PostToolUse hooks do not fire for subagent (Agent tool) tool calls
Bug Report
Claude Code version: 2.1.76
Platform: Linux (Fedora/Bazzite, kernel 6.17.7)
Shell: bash
Summary
When the main session spawns subagents via the Agent tool, any Bash, Edit, Write, Read, or Grep tool calls made by the subagent do not trigger PreToolUse or PostToolUse hooks configured in ~/.claude/settings.json. Only tool calls made directly by the main session thread fire the hooks.
This means any compliance, auditing, or safety hooks that depend on intercepting tool calls are silently bypassed when work is delegated to subagents — which is the recommended pattern for parallelized workflows.
Reproduction Steps
- Configure a
PostToolUsehook in~/.claude/settings.json:
``json``
{
"hooks": {
"PostToolUse": [{
"matcher": "Bash",
"hooks": [{
"type": "command",
"command": "/path/to/protocol-tracker.sh",
"timeout": 5
}]
}]
}
}
- The hook script increments a counter in a JSON file whenever a
git commitcommand is detected in Bash tool calls.
- In a session, run
git commitdirectly from the main thread — the hook fires and the counter increments. This works correctly.
- In the same session, spawn an Agent that runs
git commitinside its Bash calls — the hook does not fire. The counter stays at zero.
- At end of session, the state file shows
{"commits": 0}despite multiple commits having been made by subagents.
Expected Behavior
PreToolUse and PostToolUse hooks should fire for all tool calls within a session, regardless of whether the call originates from the main session or a subagent spawned by the Agent tool. The hook's stdin JSON should include an agent_id or source field to distinguish which agent made the call.
Actual Behavior
Hooks only fire for main-session tool calls. Subagent tool calls bypass all configured hooks entirely. There is no warning or indication that hooks are being skipped.
Impact
This is a compliance and safety gap for any workflow that uses hooks for:
- Audit trail tracking (counting commits, ticket creation, deployments)
- Security gates (blocking dangerous commands, checking for secrets in edits)
- Protocol enforcement (ensuring business gates are checked before mutations)
In our case, we use hooks for gate enforcement (PreToolUse:Bash) and protocol compliance tracking (PostToolUse:Bash). Both are silently bypassed when we parallelize work via the Agent tool, which is the primary recommended workflow for performance.
Workaround
We query the actual systems of record (git log, Jira API) at checkpoint time rather than trusting hook-maintained counters, and reconcile the state file afterward. This makes the state file eventually consistent but loses the real-time warning capability.
Related Issues
- #26923 — PreToolUse hook exit code 2 does not block Task tool calls
- #5812 — Feature: Allow hooks to bridge context between subagents and parent
- #16126 — Feature: Add agent identity to PreToolUse hook data
- #14859 — Feature: Agent hierarchy in hook events
- #22665 — Subagent does not inherit permission allowlist from settings.json
Suggested Fix
- Subagents should inherit and execute parent session hooks — same hooks, same matchers, same enforcement
- Hook stdin JSON should include agent context —
agent_id,parent_agent_id,agent_namefields so hooks can distinguish source - Documentation should note this limitation if inheritance is intentionally excluded
Showing cached comments. Read the full discussion on GitHub ↗
7 Comments
Found 1 possible duplicate issue:
This issue will be automatically closed as a duplicate in 3 days.
🤖 Generated with Claude Code
Can confirm — subagent tool calls don't trigger parent-session hooks. I run subagents extensively (Explore, general-purpose, etc.) and none of their tool calls hit my PreToolUse/PostToolUse hooks.
This creates a fundamental trust gap: hooks are the enforcement layer, but the more you parallelize via subagents (which is the recommended pattern for complex tasks), the less enforcement you actually get. An audit hook that counts
git commitcalls will undercount. A safety hook that gates destructive Bash commands won't fire. The sophistication of the workflow is inversely proportional to the coverage of the safety net.Hook inheritance from parent should be the default — subagents running in the same session context should respect the same policy. The
agent_id/parent_agent_idfields in stdin JSON would be essential for hooks that need to distinguish source (e.g., different thresholds for main vs subagent).Worth noting that #22665 (subagent doesn't inherit permission allowlist) describes the same pattern — subagent isolation is too aggressive in several dimensions, not just hooks.
The workaround of querying systems of record (
git log, API state) at checkpoints instead of trusting hook counters is solid, but it shifts the burden from declarative policy to imperative polling, which defeats the purpose of the hook system.Still actively impacted by this issue. Our platform has 66 hooks — PreToolUse hooks not firing for sub-agent tool calls means our safety hooks (model-override-guard, protect-constitution, etc.) are bypassed in sub-agent context. PostToolUse confirmed working in sub-agents (tested v2.1.89), but PreToolUse blocking is critical for enforcement. Would appreciate this staying open.
reproduced on v2.1.111
Empirical update — verified false in CLI 2.1.119 on Windows 11 / Git Bash.
Built a no-op JSON-logger PreToolUse hook (bash, reads stdin, appends to a JSONL file) and exercised it via
claude -p --settings <scratch.json> --include-hook-eventswith a prompt that uses the Task tool to spawn ageneral-purposesubagent that runsBash ls /tmp/.Two PreToolUse fires were captured in the same session:
Agent-tool invocation (the Task call itself) — payload containedtool_name: "Agent",tool_input.subagent_type: "general-purpose", noagent_id/agent_type(correctly — this is the parent context).Bashcall (~5 sec later) — payload containedtool_name: "Bash",tool_input.command: "ls /tmp/", AND bothagent_id: "a9067acde1e8b8e72"andagent_type: "general-purpose".PostToolUse for the subagent's Bash also fired with the same
agent_id/agent_typepopulated. SubagentStart and SubagentStop fired similarly with the subagent's identity.So the documented behavior in https://code.claude.com/docs/en/agent-sdk/hooks ("agent_id and agent_type are populated when the hook fires inside a subagent") matches what 2.1.119 actually does on Windows. If the original repro was on an older CLI version, this may be a "fixed in current" situation worth closing with that note. If it still reproduces on a current Linux build, there may be a platform-specific gap worth investigating separately.
Happy to share the test scratch config if useful — it's just a no-op logger script + a settings.json fragment wiring it to PreToolUse / PostToolUse / SubagentStart / SubagentStop.
Closing for now — inactive for too long. Please open a new issue if this is still relevant.
Please fix it! Bug is still there. Its a huge issue for any real corporate use the audit trail and monitoring and tokenomics are a MUST.