dontAsk: Write allow-list broken for all path forms on Windows native (2.1.145); PreToolUse hook confirmed workaround
Summary
--permission-mode dontAsk denies Write calls on Windows native (not WSL2) regardless of the --allowedTools Write(...) pattern used. Every path form was tested systematically at Claude Code 2.1.145 — all fail. A PreToolUse hook works correctly as a workaround.
Extends prior reports: #40076 (WSL2, ** glob, v2.1.86), #52962 (WSL2, multiple glob forms, v2.1.117). This extends both to native Windows and adds a confirmed workaround.
Environment
- Claude Code: 2.1.145
- OS: Windows 11 (native — not WSL2)
- Python: 3.14.0 (spike harness only; Claude Code itself is Node)
- Auth: Max subscription (OAuth login, no API key)
Tested allow-list forms — all DENIED
| Form | Pattern example | Outcome |
|---|---|---|
| Backslash absolute glob (W1) | Write(C:\tmp\work\**) | DENIED |
| Forward-slash exact path (W1a) | Write(C:/tmp/work/target.txt) | DENIED |
| Relative pattern (W1b) | Write(./target.txt) with CWD=workdir | DENIED |
| Forward-slash absolute glob (W1c) | Write(C:/tmp/work/**) | DENIED |
All tested via claude --permission-mode dontAsk --allowedTools "<pattern>" -p "..." at the same version on the same machine.
Root cause evidence
A PreToolUse hook records the exact tool_input the Write tool delivers:
{
"tool_name": "Write",
"tool_input": {
"file_path": "C:\Users\...\target.txt",
"content": "..."
}
}
The Write tool always delivers file_path with Windows backslashes. The --allowedTools matcher appears to compare against this path without normalizing separators. Forward-slash patterns (W1a, W1c) cannot match a backslash path. For the backslash pattern (W1), the ** glob may not traverse backslash separators.
Note: Bash(echo *), Bash(pytest *), and similar allow-list patterns do work under the same conditions — the Bash glob matcher operates on command strings that contain no path separators.
Workaround (confirmed working)
Configure a PreToolUse hook in .claude/settings.json instead of relying on --allowedTools for Write path filtering:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "python",
"args": ["/path/to/write_guard.py"]
}
]
}
]
}
}
The hook receives file_path on stdin and can normalize separators (replace("\\", "/")) before comparing against allowed paths. Hooks fire correctly under --permission-mode dontAsk on Windows native.
Reproduction
import subprocess, sys, tempfile, pathlib
with tempfile.TemporaryDirectory() as tmp:
tmp = pathlib.Path(tmp)
target = tmp / "test.txt"
allowed = f"Write({tmp.as_posix()}/**)" # forward-slash form
result = subprocess.run(
["cmd", "/c", "claude", "--permission-mode", "dontAsk",
"--allowedTools", allowed, "-p",
f"Use the Write tool to create {target} containing 'hello'."],
capture_output=True, text=True, cwd=str(tmp)
)
print("created:", target.exists()) # prints False — should be True
Related
- #40076 — same glob-matching failure in WSL2 at v2.1.86 (closed/stale)
- #52962 — same failure in WSL2 at v2.1.117, multiple glob forms (closed/duplicate)
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗