PreToolUse hook on Agent tool fires but does not block execution
Summary
PreToolUse hook with matcher: "Agent" fires correctly (stdin payload received, script executes, exit code 1 returned) but does not actually block the Agent tool from executing. The Explore subagent proceeds regardless of the deny signal.
Environment
- Claude Code CLI (macOS, Darwin 25.4.0)
- Model: claude-sonnet-4-6
- Permission mode: plan
Setup
.claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Agent",
"hooks": [
{
"type": "command",
"command": "python3 .claude/block-explore.py"
}
]
}
]
}
}
block-explore.py:
import json, sys
payload = json.loads(sys.stdin.read())
desc = payload.get('tool_input', {}).get('description', '').lower()
if 'explore' in desc:
print('BLOCKED: Use pre-built context files instead of Explore agent.')
sys.exit(1) # Should deny the tool
What happens
- Hook script runs ✅ (confirmed via debug log — full payload received)
- Script detects keyword in description ✅
- Script prints deny message and exits with code 1 ✅
- Agent/Explore tool executes anyway ❌
Debug log (actual payload received by hook)
{
"session_id": "...",
"tool_name": "Agent",
"tool_input": {
"description": "Explore some service methods",
"prompt": "Search for patterns in the codebase...",
"subagent_type": "Explore"
},
"hook_event_name": "PreToolUse"
}
Expected behavior
Exit code 1 from a PreToolUse hook should deny/block the Agent tool call, same as it does for Bash, Edit, and other tools.
Use case
We maintain pre-built context maps (wiki + knowledge graph notes) for each project. We want to enforce a lookup-first strategy via hooks — read pre-built context before allowing broad file exploration. Without the ability to block the Agent tool via hooks, Claude defaults to Explore agents (~50K tokens) instead of targeted reads from pre-built context (~4K tokens).
Workarounds attempted
- CLAUDE.md instructions — ignored for complex planning tasks
- hookify plugin
action: blockon prompt event — does not intercept Agent tool - PreToolUse hook with exit code 1 — fires but doesn't block (this issue)
🤖 Generated with Claude Code
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗