Windows: path-scoped Write/Edit permission rules never match — Write tool absolutizes file_path pre-check and absolute Windows paths match no documented pattern form

Open 💬 1 comment Opened Jun 12, 2026 by Masbaker96

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?

Environment

  • Claude Code CLI 2.1.175 (also reproduced on 2.1.174)
  • Windows 11 Home 10.0.26200, PowerShell 7
  • Subscription auth (OAuth / claude.ai login), no ANTHROPIC_API_KEY in env
  • Headless invocations: claude -p --output-format text --setting-sources user --tools "Read,Write,Edit" --allowedTools "Read" "<rule under test>" with cwd = C:\Projects\po-claude\handoff\workspace, prompt piped via stdin

Summary

On Windows, no path-scoped Write(...) permission rule form authorizes a Write tool call in headless mode — not via --allowedTools, not via --settings '{"permissions":{"allow":[...]}}'. The documented Windows behavior ("paths are normalized to POSIX form before matching. C:\Users\alice becomes /c/Users/alice, so use //c/**/..." — https://code.claude.com/docs/en/permissions.md) does not hold: the documented //c/... form is denied, and so is the maximal //** (documented as matching any absolute path on any drive). A bare unscoped Write allow rule passes the identical target, so the failure is in pattern matching, not tool availability.

A --output-format stream-json transcript additionally shows the Write tool absolutizes file_path before the permission check: when the model passes file_path: "./absprobe-p18.txt" verbatim (cwd-relative), the denial message names the absolutized path (C:\Projects\po-claude\handoff\workspace\absprobe-p18.txt), and cwd-relative rules (Write(./**) + Write(./*)) do not match it. Net effect on Windows: path-scoped Write/Edit rules are inert — the only working grant is unscoped Write, which defeats path-scoped least-privilege for headless automation.

Repro (minimal)

# cwd: C:\Projects\po-claude\handoff\workspace
"Use the Write tool to create the file C:\Projects\po-claude\handoff\workspace\probe.txt with content OK. Reply WRITE-OK or WRITE-DENIED." |
  claude -p --output-format text --setting-sources user --model haiku `
    --tools "Read,Write,Edit" `
    --allowedTools "Read" "Write(//c/Projects/po-claude/handoff/workspace/**)"
# -> WRITE-DENIED ("Claude requested permissions to write to ... but you haven't granted it yet")
# -> probe.txt NOT created

Control — same target, bare rule:

... --allowedTools "Read" "Write"
# -> file IS created (write succeeds)

Probe matrix (all DENIED unless noted; file-creation checked on disk after each run)

| # | Rule form | Rule source | Target | Result |
|---|---|---|---|---|
| P1 | Write(C:\Projects\po-claude\handoff\workspace\**) | --allowedTools | abs, dir root | DENIED |
| P2 | Write(C:/Projects/po-claude/handoff/workspace/**) | --allowedTools | abs, dir root | DENIED |
| P3 | Write(//c/Projects/po-claude/handoff/workspace/**) (docs form) | --allowedTools | abs, dir root | DENIED |
| P4 | Write(//c/Projects/po-claude/handoff/workspace/*) | --allowedTools | abs, dir root | DENIED |
| P5 | Write(//c/Projects/po-claude/handoff/workspace/**) | --allowedTools | abs, subdirectory | DENIED |
| P6 | Write(//C/Projects/po-claude/handoff/workspace/**) | --allowedTools | abs, dir root | DENIED |
| P7 | Write(//c:/Projects/po-claude/handoff/workspace/**) | --allowedTools | abs, dir root | DENIED |
| P8 | Write(./**) + Write(./*) | --allowedTools | model told ./file | DENIED |
| P9 | Write (bare, unscoped) | --allowedTools | abs, dir root | ALLOWED — file created |
| P10 | Write(//c/Projects/po-claude/handoff/workspace/**) | --settings '{"permissions":{"allow":[...]}}' | abs, dir root | DENIED |
| P11 | Write(//C:/Projects/po-claude/handoff/workspace/**) | --allowedTools | abs, dir root | DENIED |
| P13 | Write(//c/projects/po-claude/handoff/workspace/**) (all-lowercase) | --allowedTools | abs, dir root | DENIED |
| P14 | Write(//**) (docs: any absolute path, any drive) | --allowedTools | abs, dir root | DENIED |
| P18 | Write(./**) + Write(./*), model forced to pass file_path exactly ./absprobe-p18.txt | --allowedTools | relative string | DENIED |

User-level ~/.claude/settings.json contains no permissions block (interference ruled out). Project settings excluded via --setting-sources user.

stream-json evidence (P18 — absolutization before permission check)

TOOL_USE  Write input={"file_path":"./absprobe-p18.txt","content":"OK"}
TOOL_RESULT (is_error=True): Claude requested permissions to write to
  C:\Projects\po-claude\handoff\workspace\absprobe-p18.txt, but you haven't granted it yet.
RESULT permission_denials=[{"tool_name":"Write","tool_input":{"file_path":"./absprobe-p18.txt",...}}]

(P12 shows the same denial with the model passing the absolute path directly, under the docs-form //c/... rule.)

Expected vs actual

  • Expected (per docs): Write(//c/Projects/po-claude/handoff/workspace/**) authorizes writes anywhere under that directory; //** authorizes any absolute path; dir/** is root-level-inclusive.
  • Actual: every path-scoped form is denied; only unscoped Write authorizes.

Impact

Headless automation on Windows cannot express a least-privilege write jail with permission rules at all. The available workarounds are unscoped Write (over-broad) or --permission-mode acceptEdits (directory-scope rather than rule-scope). Docs and behavior need reconciliation either way.

Related (searched before filing; none identical)

  • #67610 — hook if path patterns with / separators never match on Windows (adjacent Windows path-matching surface)
  • #36884 — VS Code extension ignores Edit/Write permission rules in settings files
  • #6881 — glob patterns (/**) in settings.local.json don't work (possibly same matcher root cause; this report adds headless --allowedTools/--settings coverage + stream-json absolutization evidence on Windows)

Secondary observation (separate issue if useful)

One headless run (P9) completed its Write successfully and then hung for ~25 minutes without exiting (killed externally). Happy to file separately with details.

What Should Happen?

Per https://code.claude.com/docs/en/permissions.md, a path-scoped rule like Write(//c/Projects/po-claude/handoff/workspace/**) (Windows paths normalized to POSIX form, ** root-inclusive) should authorize Write tool calls targeting that directory in headless mode — the write should succeed and the file should be created. Equivalently, Write(//**) should authorize any absolute path, and cwd-relative rules should match a cwd-relative file_path. Instead, every path-scoped form is denied and only bare unscoped Write authorizes.

Error Messages/Logs

TOOL_USE  Write input={"file_path":"./absprobe-p18.txt","content":"OK"}
TOOL_RESULT (is_error=True): Claude requested permissions to write to C:\Projects\po-claude\handoff\workspace\absprobe-p18.txt, but you haven't granted it yet.
RESULT permission_denials=[{"tool_name":"Write","tool_input":{"file_path":"./absprobe-p18.txt",...}}]

Plain-text response variants across probes: "WRITE-DENIED: permission not granted" / "Permission not granted to write file" / "permissions not granted" / "permission required"

Steps to Reproduce

  1. On Windows, open PowerShell with cwd = any directory (example uses C:\Projects\po-claude\handoff\workspace).
  2. Run the documented Windows path-scoped rule form against a target inside that directory:
"Use the Write tool to create the file C:\Projects\po-claude\handoff\workspace\probe.txt with content OK. Reply WRITE-OK or WRITE-DENIED." |
  claude -p --output-format text --setting-sources user --model haiku `
    --tools "Read,Write,Edit" `
    --allowedTools "Read" "Write(//c/Projects/po-claude/handoff/workspace/**)"
  1. Observe: response is WRITE-DENIED and probe.txt is NOT created.
  2. Re-run the identical command with --allowedTools "Read" "Write" (bare, unscoped): the write succeeds and the file IS created — so the tool works; the path-scoped matcher is what fails.
  3. Repeat step 2 with any other rule form (C:\...\**, C:/.../**, //C/, //c:/, //C:/, all-lowercase, //**, ./**+./*, or the same rules via --settings '{"permissions":{"allow":[...]}}'): all DENIED — full matrix in the description above.
  4. Optional: re-run with --output-format stream-json and force a cwd-relative file_path (./probe.txt) in the prompt — the denial names the absolutized C:\... path, showing absolutization happens before the permission check.

Claude Model

Other

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.175 (Claude Code)

Platform

Anthropic API

Operating System

Windows

Terminal/Shell

PowerShell

Additional Information

_No response_

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗