PowerShell tool: destructive-path guard mis-extracts an unrelated quoted token as the delete target (false-positive blocks)
Bug: PowerShell-tool destructive-path guard mis-extracts an unrelated quoted token as the delete target
Summary
Claude Code's built-in protected-path guard (the one that blocks destructive file ops on system/root paths with "Remove-Item on system path 'X' is blocked. This path is protected from removal.") does a crude static scan of the whole command for a path-like/quoted token near Remove-Item, and can grab a token from somewhere other than the actual delete target. When that wrong token happens to look root-like (e.g. a leading-slash string) or is just an unrelated quoted literal, the guard blocks a command that never targeted a protected path. The legitimate Remove-Item in these cases targeted an ordinary repo-relative file (e.g. .git/index.lock).
Environment
- Claude Code on Windows 11, PowerShell tool (
CLAUDE_CODE_USE_POWERSHELL_TOOL=1, pwsh 7). - Guard fires before execution; no user hook is involved (confirmed: the message string exists in no settings file, hook, or managed-settings path — it's the built-in).
Steps to reproduce / observed cases
Two real false-positives, both in compound (;-joined) commands whose only Remove-Item targeted a benign relative path:
- Commit message with a slash-token. A command that deleted
.git/index.lockand then rangit commitwhose message contained the substring/synthesize.
- Expected: lock removed, commit made.
- Actual: blocked — "Remove-Item on system path '/synthesize' is blocked. This path is protected from removal." The guard grabbed the commit-message text, not the
Remove-Itemargument.
-splitregex literal. A command of the shapeRemove-Item <relative-path> ... ; ($line -split '\s+').
- Expected: file removed, split runs.
- Actual: blocked — "Remove-Item on system path '\s+' is blocked. This path is protected from removal." The guard grabbed the regex literal
'\s+'as the "path."
A related, previously-noted variant: when the Remove-Item target is an unresolvable variable (e.g. Remove-Item $lock), the guard assumes / (root) and blocks.
Intermittency
The mis-extraction is inconsistent — minimal synthetic repros (e.g. Remove-Item .\nonexistent.txt -ErrorAction SilentlyContinue; $m = "add /synthesize route") did not trip it, while the real-world compound commands above did. So the trigger depends on command shape (likely the position/quoting of tokens relative to Remove-Item), not merely the presence of a slash-string. This makes it hard for users to predict or avoid.
Impact
- Legitimate commits and cleanup commands are blocked, wasting tool calls and forcing awkward workarounds (
[System.IO.File]::Delete('<literal>'), splitting commands, or falling back to Bashrm -f). - The guard cannot be disabled or scoped by the user, so there is no configuration-level mitigation — only command-rewriting.
Suggested fix
Parse the actual Remove-Item target rather than any quoted substring in the command:
- Bind to the value of
-Path/-LiteralPath, or the first positional argument of theRemove-Iteminvocation specifically. - Restrict protected-path matching to tokens that are genuine filesystem paths (drive-letter
C:\…, UNC\\…, or resolvable repo-relative paths). Ignore regex/option strings, command-argument values of other commands in the pipeline (e.g.git commit -m …), and unrelated quoted text. - For unresolvable-variable targets, prefer "allow + warn" (or resolve the literal assignment when present) over assuming
/.
Keep the genuine protection — Remove-Item C:\Windows\…, Remove-Item /, recursive-force on system roots should still block. The ask is only to stop blocking commands that merely mention a path-like or quoted string elsewhere.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗