[BUG] Hook `if` field: path patterns with explicit `/` separators never match on Windows
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
In hook if conditions, file-path patterns containing an explicit / separator (e.g. Write(subdir/**), Write(**/subdir/inner/**)) never match on Windows — the hook silently never fires. Only globstar-pure patterns without any / (e.g. Write(**subdir**), Write(**.py)) match. Matching is also case-sensitive (Write(**SUBDIR**) does not match subdir), while Windows paths are not.
This is not the permission-engine path bug fixed in 2.1.162 ("permission rules backslash/case"): since that release, allow/deny permission rules match Windows paths correctly, but the hook if field still fails — the two matchers appear to be separate engines. Existing issues #64432 / #55674 cover the permission-engine side only.
The docs state the if field "accepts the same patterns as permission rules: Bash(git *), Edit(*.ts)" — with no Windows caveat, so this looks like a genuine bug rather than documented behavior.
Deterministic across repeated runs; reproduced on 2.1.143, 2.1.162, 2.1.165, 2.1.167 and 2.1.173.
What Should Happen?
All path patterns that match the written file should fire their hooks. For a Write of subdir/inner/config.py, the spies with if: "Write(subdir/**)" and if: "Write(**/subdir/inner/**)" should fire exactly like the globstar-pure if: "Write(**subdir**inner**)" does (and matching should arguably be case-insensitive on Windows, since the filesystem is).
Error Messages/Logs
No error output — the failure is silent: hooks with `/`-containing patterns simply never run.
markers.log after the repro run (only globstar-pure + catch-all fired):
M_GLOBSTAR
ALL
Steps to Reproduce
- On Windows, create a sandbox folder (e.g.
C:\hooktest\) with.claude\settings.json:
{
"permissions": { "allow": ["Write(*)", "Read(*)"] },
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{ "type": "command", "command": "bash .claude/spy.sh M_GLOBSTAR", "if": "Write(**subdir**inner**)" },
{ "type": "command", "command": "bash .claude/spy.sh S_SLASH", "if": "Write(subdir/**)" },
{ "type": "command", "command": "bash .claude/spy.sh S_SLASH2", "if": "Write(**/subdir/inner/**)" },
{ "type": "command", "command": "bash .claude/spy.sh CASE", "if": "Write(**SUBDIR**INNER**)" },
{ "type": "command", "command": "bash .claude/spy.sh ALL", "if": "Write(**)" }
]
}
]
}
}
- Create
.claude/spy.sh(marker side-effect on the filesystem):
#!/bin/bash
cat >/dev/null 2>&1
printf '%s\n' "$1" >> markers.log
exit 0
- Run headless (prompt via stdin so
$()/globs are not expanded by the outer shell):
cd /c/hooktest
echo 'Use the Write tool to create the file subdir/inner/config.py containing exactly: # hook matcher test. Do nothing else.' | claude -p --permission-mode bypassPermissions --allowedTools Write > /dev/null
cat markers.log
- Expected:
M_GLOBSTAR,S_SLASH,S_SLASH2,ALL(plusCASEif matching were case-insensitive).
Actual: only M_GLOBSTAR and ALL — every pattern containing / (relative or anchored) never fires, CASE never fires.
Claude Model
None
Is this a regression?
No, this never worked
Last Working Version
_No response_
Claude Code Version
2.1.173 (Claude Code)
Platform
Anthropic API
Operating System
Windows
Terminal/Shell
VS Code integrated terminal
Additional Information
- Model-independent (the
ifmatcher runs in the harness around the tool call); reproduced with multiple models. - Native Windows install; hooks run via Git Bash (bare
bashon PATH). - Current workaround: globstar-pure patterns without
/(e.g.Edit(**myproject**src**)), treated as case-sensitive. - We maintain a spy-hook test bench and have re-verified this on every recent release — happy to provide further matrices.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗