Bug Description
PreToolUse hooks that return permissionDecision: "deny" are not enforced when the target tool is an MCP server tool. The hook fires correctly and returns a valid deny response, but the MCP tool call proceeds anyway.
Reproduction Steps
- Register a PreToolUse hook in
~/.claude/settings.json targeting an MCP tool:
{
"hooks": {
"PreToolUse": [
{
"hooks": [
{
"command": "/path/to/hook.sh",
"type": "command"
}
],
"matcher": "mcp__my_server__my_tool"
}
]
}
}
- The hook script reads stdin and returns a deny decision:
#!/bin/bash
INPUT=$(cat)
echo "BLOCKED: reason here"
echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"reason here"}}'
exit 0
- When Claude calls the MCP tool, the hook fires and returns deny, but the MCP tool call is executed anyway.
Expected Behavior
The MCP tool call should be blocked, and the model should see the deny reason — identical to how PreToolUse deny works for built-in tools (Bash, Read, Write, etc.).
Actual Behavior
The hook fires, returns permissionDecision: "deny", but the MCP tool call proceeds and succeeds. The deny decision is effectively ignored.
Context
This was discovered while running multiple Claude Code instances in tmux panes with an identity enforcement hook. The hook correctly detects when an agent tries to send a message as a different identity via an MCP-based messaging server, returns deny, but the message is sent anyway.
Verified:
- Hook script works correctly when tested manually (
echo '...' | hook.sh)
- Hook IS registered with the correct matcher pattern
- Built-in tool PreToolUse deny IS enforced (e.g., Bash hooks work)
- MCP tool PreToolUse deny is NOT enforced
Environment
- Claude Code CLI
- Linux
- MCP server type: HTTP (
"type": "http")
Bug Description
PreToolUsehooks that returnpermissionDecision: "deny"are not enforced when the target tool is an MCP server tool. The hook fires correctly and returns a valid deny response, but the MCP tool call proceeds anyway.Reproduction Steps
~/.claude/settings.jsontargeting an MCP tool:{ "hooks": { "PreToolUse": [ { "hooks": [ { "command": "/path/to/hook.sh", "type": "command" } ], "matcher": "mcp__my_server__my_tool" } ] } }Expected Behavior
The MCP tool call should be blocked, and the model should see the deny reason — identical to how PreToolUse deny works for built-in tools (Bash, Read, Write, etc.).
Actual Behavior
The hook fires, returns
permissionDecision: "deny", but the MCP tool call proceeds and succeeds. The deny decision is effectively ignored.Context
This was discovered while running multiple Claude Code instances in tmux panes with an identity enforcement hook. The hook correctly detects when an agent tries to send a message as a different identity via an MCP-based messaging server, returns deny, but the message is sent anyway.
Verified:
echo '...' | hook.sh)Environment
"type": "http")