Bash permission allow-list rules fail to match Windows drive-letter paths

Status Closed — not planned
Maintainer reply None cached
Activity 4 comments · opened Jul 10, 2026 · closed Sep 27, 2026

Bug: Bash permission allow-list rules fail to match for Windows drive-letter paths

Summary

Permission rules in .claude/settings.json that reference an absolute Windows path with a drive letter (e.g. C:/Program Files/... or /c/Program Files/...) never match the command they're written for — regardless of whether the rule is a wildcard or an exact literal match, and regardless of quoting style. Plain commands with no path (echo *, grep *) match correctly every time, including when combined with pipes, redirects, and parentheses in arguments.

This forces a fresh "Allow this bash command?" prompt on every invocation of tools like Blender or Unity launched by absolute path, even when a rule for that exact command already exists in the allow list. Evidence of long-term impact: the affected user's global ~/.claude/settings.json has accumulated dozens of near-duplicate one-off exact-match rules for the same Blender executable (different scripts/args each time) — consistent with this having silently failed for a long time, with each approval only ever saving a new one-off rule rather than actually generalizing.

Environment

  • OS: Windows 11 Pro 10.0.26200
  • Shell: Git Bash (via Claude Code's Bash tool)
  • Permission mode: project .claude/settings.json sets "defaultMode": "dontAsk"

Repro steps

Project .claude/settings.json contains (among others):

"Bash(C:/PROGRA~1/BLENDE~1/BLENDE~1.1/blender.exe *)",
"Bash(\"/c/Program Files/Blender Foundation/Blender 5.1/blender.exe\" *)",
"Bash(\"/c/Program Files/Blender Foundation/Blender 5.1/blender.exe\" --version)"
  1. Run C:/PROGRA~1/BLENDE~1/BLENDE~1.1/blender.exe --version → prompts, despite matching the first (wildcard) rule as a plain prefix.
  2. Run "/c/Program Files/Blender Foundation/Blender 5.1/blender.exe" --version → prompts, despite being a byte-for-byte match of the third (exact, non-wildcard) rule.
  3. Compare against Bash(echo *) / Bash(grep *), both present in the same file: any invocation of echo or grep, including with pipes (|), 2>&1 redirects, and literal parentheses in quoted arguments, matches its rule correctly and never prompts.
  4. Reconstructing the exact failing Blender command's argument shape (same quoting, -- separator, 2>&1, multi-flag grep -e) but substituting echo for blender.exe also passes cleanly with no prompt — ruling out pipe/redirect/quote/parentheses handling as the cause and isolating the failure specifically to path-based rules.

Expected behavior

A command that is a literal prefix match (wildcard rule) or literal exact match (non-wildcard rule) of an existing allow-list entry should execute without a permission prompt, regardless of whether the pattern contains a Windows drive-letter path.

Actual behavior

Path-based rules (short 8.3 path form, long quoted form, wildcard, and exact) all fail to match. Only rules with no absolute path in them appear to match reliably.

Secondary bug observed during investigation: defaultMode: "dontAsk" not honored

With "defaultMode": "dontAsk" set in .claude/settings.json, any command not explicitly present in the allow list still produces a permission prompt — behavior indistinguishable from "default" mode. Either "dontAsk" is not a recognized value and silently falls back, or it isn't wired into the Bash permission check path.

Tertiary bug observed during investigation: allow-list edits don't hot-reload mid-session

Adding a brand-new, deliberately simple rule (Bash(bash tools/run_blender.sh *) — no drive letter, no quotes) to .claude/settings.json via a file edit mid-session did not take effect; the very next invocation of that exact command still prompted. This suggests the permission allow-list is loaded once at session start rather than watched for live changes (unlike some other settings-driven features which are known to require a /hooks-style reload).

Impact

For any project-based tooling that shells out to an absolute-path binary on Windows (game-dev pipelines calling Blender, Unity, etc. by full path), the allow-list is effectively non-functional — every invocation prompts, and approvals don't generalize, leading to unbounded accumulation of near-duplicate rules in global settings over time.

Workaround in use

Wrapping the absolute-path binary in a thin shell script referenced by a bare relative path (no drive letter, no spaces) and allow-listing the wrapper invocation instead, e.g.:

# tools/run_blender.sh
#!/bin/bash
exec "C:/Program Files/Blender Foundation/Blender 5.1/blender.exe" "$@"
"Bash(bash tools/run_blender.sh *)"

Not yet confirmed effective — see tertiary bug above (requires a session restart to test, since the rule was added mid-session).

View original on GitHub ↗

3 Comments

rwil02 · 3 months ago

Prompts every time for:
& "e:\Files\GIT\MillManager.NET\.claude\scripts\parse-dialog-xml.ps1" -XmlPath "Export2026\RptDialog_Delivery_NIRResult_Label.xml"

What is in my settings.json permissions.allow:

      "PowerShell(& \"e:\\Files\\GIT\\MillManager.NET\\.claude\\scripts\\parse-dialog-xml.ps1\"*)",
      "PowerShell(& \"e:\\\\Files\\\\GIT\\\\MillManager.NET\\\\.claude\\\\scripts\\\\*)",
      "PowerShell(& 'e:\\\\Files\\\\GIT\\\\MillManager.NET\\\\.claude\\\\scripts\\\\*)",
      "PowerShell(& \"e:\\Files\\GIT\\MillManager.NET\\.claude\\scripts\\parse-dialog-xml.ps1\" -XmlPath *)",
vincebowdrentribal · 2 months ago

Possibly the same root cause manifesting without a path in the rule pattern itself.

Environment: Windows 11 Pro, Git Bash via Claude Code's Bash tool, project .claude/settings.json (no defaultMode override — default "default" mode).

Repro:

.claude/settings.json contains:

"Bash(cygpath *)"

Running cygpath -w /c/edgeteam still prompts for approval, despite matching this wildcard rule as a plain prefix. Note the rule itself contains no path — only the matched argument (/c/edgeteam) is a drive-letter-style path. Confirmed reproducible in three separate sessions, including a brand-new session started specifically to rule out stale/mid-session state.

As a control, other wildcard rules in the exact same settings file, with no path anywhere in rule or argument, match correctly with no prompt (e.g. Bash(az repos pr show *) → az repos pr show --help passes silently).

This suggests the failure isn't limited to rules that reference a drive-letter path in their pattern text (as in the original report) — a drive-letter-style path appearing only in the matched argument also seems to break the match. Might be a case worth folding into this issue if the underlying cause turns out to be the same path-normalization/tokenization step.

Also independently reproduced: the tertiary bug noted here (allow-list edits don't hot-reload mid-session) — added a brand-new rule for a made-up command via a live file edit and it still prompted on the very next invocation in the same session; a fresh session afterward picked it up correctly for other rules but not for the cygpath case above.

derikl · 2 months ago

Same defect reproduces for PowerShell rules on the Claude Desktop Code tab (Windows 11, 26200; Desktop 1.20186.0.0), and the probing pins it to one property: a filesystem path inside the wildcard pattern — not the drive letter, the & call operator, or separator/escaping style.

In a trusted workspace, PowerShell(git *) matches everything tested, including commands with backslash-path and quoted arguments (git -C C:\repos\myrepo log -1 --format="%h %s" is silent). But PowerShell(& C:\claude\workspace\scripts\*) never matches & C:\claude\workspace\scripts\scripttest.ps1 -Path "version".

Fresh-session 4-way matrix, all rules staged in settings.local.json from session start, workspace trusted:

| Probe | Command shape | Staged rule | Result |
|---|---|---|---|
| a | & C:/…/scripts/scripttest.ps1 -Path "version" | PowerShell(& C:/…/scripts/*) | prompted |
| b | C:/…/scripts/scripttest.ps1 -Path "version" | PowerShell(C:/…/scripts/*) | prompted |
| c | & C:\…\scripts\scripttest.ps1 -Path "version" | PowerShell(& C:\…\scripts\*) (+ doubled-backslash) | prompted |
| d | C:\…\scripts\scripttest.ps1 -Path "version" | PowerShell(C:\…\scripts\*) (+ doubled-backslash) | prompted |
| e | git -C C:\repos\<repo> status | PowerShell(git *) | silent |

So a PowerShell(...) wildcard whose prefix is a path never matches, while a path-free command-name wildcard matches even when the command contains backslash paths. Notably, harness-recorded exact entries for the same commands (stored with doubled backslashes) do match — the exact-match path unescapes/compares correctly while the wildcard path does not. Net effect: script-prefix rules can't be made to work, so those calls prompt once per unique argument string, forever.

Showing cached comments. Read the full discussion on GitHub ↗