PreToolUse hooks and permissions.deny are not evaluated at all in some sessions (Bash/Read reach denied paths unblocked)
Title
PreToolUse hooks and permissions.deny are not evaluated at all in some sessions (Bash/Read reach denied paths unblocked)
Summary
In a project session, both permissions.deny rules and a registered PreToolUse hook failed to block Bash and Read tool calls targeting a path they should have denied. Debug logging added to the hook script shows the hook process is never invoked for these calls — this isn't a hook logic bug, the hook simply doesn't run.
Environment
~/.claude/settings.json(global):{"permissions": {"defaultMode": "auto"}, "theme": "dark"}~/.claude/settings.local.json(global):{"sandbox": {"enabled": true, "autoAllowBashIfSandboxed": false}}- Project
.claude/settings.json:
``json``
{
"permissions": {
"deny": [
"Read(//mnt/c/realdata/**)",
"Write(//mnt/c/realdata/**)",
"Edit(//mnt/c/realdata/**)",
"Bash(*realdata*)"
]
},
"hooks": {
"PreToolUse": [
{
"matcher": "Bash|Read|Write|Edit|NotebookEdit",
"hooks": [
{
"type": "command",
"command": "python3 /path/to/.claude/hooks/block_realdata.py",
"timeout": 10
}
]
}
]
}
}
- OS: Linux (WSL2), Claude Code CLI
Steps to reproduce
- Configure a project
permissions.denyrule matching a path (e.g.Bash(*realdata*),Read(//mnt/c/realdata/**)). - Register a
PreToolUsehook forBash|Read|Write|Edit|NotebookEditthat blocks (exit code 2) when the target path/command matches the same pattern. - Verify the hook script works standalone:
echo '{"tool_name":"Bash","tool_input":{"command":"cat /mnt/c/realdata/x"}}' | python3 hook.py→ correctly exits 2 and prints a block message. - In an actual Claude Code session (global
permissions.defaultMode: "auto",sandbox.enabled: true), invoke Bash (ls/caton the denied path) or Read (file_path under the denied path).
Expected
Per the documented behavior (deny rules and explicit hooks should always be evaluated, even under auto mode and sandbox auto-allow), the call should be blocked or at minimum trigger the hook.
Actual
- The Bash/Read call executes normally and returns real file contents / directory listing from the denied path.
- Added debug logging inside the hook script (writes a line to
/tmp/block_realdata_debug.logon every invocation, before any pattern matching). After clearing the log and re-running the same Bash call against the denied path, no log entry was written — meaning the hook process was never started for that tool call. This rules out a hook-logic bug; the PreToolUse hook mechanism itself did not fire. permissions.denyalso did not block the same calls.
Additional notes
- This was reproduced twice in the same project across different sessions/days with the same result.
- No permission prompt was shown either (consistent with
defaultMode: auto), but per docs deny rules should still apply and block regardless of auto-approval. - Happy to provide the full hook script and a minimal settings.json repro if useful.