Preflight Checklist
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 (plus CASE if 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
if matcher runs in the harness around the tool call); reproduced with multiple models.
- Native Windows install; hooks run via Git Bash (bare
bash on 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.
Preflight Checklist
What's Wrong?
In hook
ifconditions, 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 matchsubdir), 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
iffield still fails — the two matchers appear to be separate engines. Existing issues #64432 / #55674 cover the permission-engine side only.The docs state the
iffield "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
Writeofsubdir/inner/config.py, the spies withif: "Write(subdir/**)"andif: "Write(**/subdir/inner/**)"should fire exactly like the globstar-pureif: "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 ALLSteps to Reproduce
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(**)" } ] } ] } }.claude/spy.sh(marker side-effect on the filesystem):$()/globs are not expanded by the outer shell):M_GLOBSTAR,S_SLASH,S_SLASH2,ALL(plusCASEif matching were case-insensitive).Actual: only
M_GLOBSTARandALL— every pattern containing/(relative or anchored) never fires,CASEnever 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
ifmatcher runs in the harness around the tool call); reproduced with multiple models.bashon PATH)./(e.g.Edit(**myproject**src**)), treated as case-sensitive.