PreToolUse hooks declared in project .claude/settings.json never fire (user-level works)
Environment
- Claude Code 2.1.257
- Windows 11 (win32), Git Bash shell
Describe the bug
A PreToolUse hook with matcher "Bash" and an "if" condition, declared in<project>/.claude/settings.json, never executes — even though the file is
valid JSON and matches the published settings schema. The identical hook
block, moved verbatim to ~/.claude/settings.json (user-level), fires
correctly and immediately in the same session, with no restart.
Steps to reproduce
- In a project, create
.claude/hooks/test.shthat appends a line to a
sentinel file.
- Add to
<project>/.claude/settings.json:
``json``
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/test.sh",
"if": "Bash(git *)"
}
]
}
]
}
}
- Validate the JSON (it's valid, matches schema).
- In the same Claude Code session, run any Bash tool call matching
git *
(e.g. git log -1).
- Observe: the sentinel file is never written — the hook does not fire.
- Move the identical
hooksblock to~/.claude/settings.json(user-level)
instead, repeat step 4.
- Observe: the hook fires immediately, sentinel is written.
Expected
A project-level PreToolUse hook should fire the same as a user-level one.
Actual
The project-level hook silently never registers/fires — no error, no
warning anywhere in the UI. Reproduced twice in one live session with two
separate trigger commands; user-level registration with the same config
fired on the very first trigger.