PowerShell tool: non-overridable "Remove-Item on system path" guard over-blocks legitimate commands (AST target mis-attribution)
Summary
The PowerShell tool blocks legitimate commands with:
Remove-Item on system path '<x>' is blocked. This path is protected from removal.
...when the command contains a Remove-Item and an unrelated token that the
static analyzer mis-attributes as the delete target. The extraction appears to pull
a token from elsewhere in the command line rather than from Remove-Item's own
arguments.
Confirmed mis-attributed shapes
- A later
-replace '\s',''operand → target parsed as''\s','' - A co-occurring REST-URL path segment → target parsed as
'/promote' - A
Join-Path-derived variable pointing at a dangling junction → target parsed as'/'
In each case the actual Remove-Item target is a legitimate, non-system path; the
blocked token comes from an unrelated part of the command.
Impact
The deny is classifierApprovable: false, so it is non-overridable — nopermissions.allow rule and no CLAUDE_CODE_DISABLE_* environment variable bypasses
it. This blocks otherwise-valid diagnostic and file-management commands.
Requested fix
- Scope the
Remove-Itemtarget extraction to that command element's own arguments
(terminate at ; / |), so tokens from later commands or unrelated operands are
not captured; and/or
- Make the guard
classifierApprovableso a reviewed command can proceed.
Workaround
Delete via .NET ([System.IO.File]::Delete / [System.IO.Directory]::Delete) or the
Bash rm tool — the guard keys on the literal cmdlet name remove-item.
Environment
- Claude Code 2.1.198
- Windows, PowerShell tool
Showing cached comments. Read the full discussion on GitHub ↗
3 Comments
Adding another mis-attribution vector for this guard: text inside a PowerShell
#comment is picked up as the delete target.Repro
A multi-statement command where a comment line contains a slash-leading token (
/init), followed by a scopedRemove-Itemon a variable path:Result (blocked before execution):
/initappears only in the comment (it refers to a slash command, not a filesystem path). The actualRemove-Itemtarget is-LiteralPath $proj, a scratchpad subfolder.Expected
Comment text (like here-string bodies in #73882 / #81378 and quoted tokens in #69461) should not be scanned as command arguments when attributing targets to
Remove-Item.Environment
Workaround
Split destructive commands into their own minimal tool call and avoid slash-leading words in comments within the same command.
🤖 Generated with Claude Code
Adding a fourth mis-attributed shape, plus a note that the drive-root rule from #78513 still fires after that issue was closed as completed.
Shape 4: target captured from a preceding statement, not an operand
The three shapes in the issue body are all operands or URL segments inside the same command element. This one crosses a statement boundary — the reported target comes from an earlier
;-separated statement:Blocked with:
Remove-Item's actual target is the relative namechild-dir. The reported targetC:\myappears nowhere in the command as a path — it is the first whitespace-delimited fragment ofSet-Location's quoted argument.The same thing happens with no
Remove-Itemin the command at all, viarmdirthrough the call operator:→ also blocked, also citing
'C:\my.The doubled quote is a useful tell
The message renders
''C:\my'— two opening quotes. The captured token has kept the opening'of the original quoted argument, which is what you would expect from splitting raw command text on whitespace rather than quote-aware or AST-based extraction. Consequence: any directory whose name contains a space truncates at its first space, and the fragment left over is what gets matched against the protected-path list.The drive-root rule from #78513 still fires
#78513 ("blocks Remove-Item for any direct child of a drive root") was closed as completed on 2026-07-30. As of 2026-08-14 this still reproduces — but it is reached through the truncation above rather than through a genuine drive-root argument.
C:\my project -folderis not a drive-root child. The truncatedC:\myis. So a fix that validates real arguments against the drive-root rule will not catch a fragment that only becomes a drive-root child after truncation. It may be worth checking whether the two share a code path — fixing the extraction (as this issue already proposes) would close this route into the drive-root rule as a side effect.Impact
Confirms the
classifierApprovable: falsenote in the body: nopermissions.allowrule clears it, and the block is independent of the OS's own permissions — the command was a legitimate cleanup of an empty, already-deregistered git worktree directory.The
[System.IO.Directory]::Delete($path, $false)workaround already listed here does work; it carries no removal keyword and passes cleanly.Environment
Correcting an implication in my comment above.
I wrote that the #78513 drive-root block "is reached through the truncation above rather than through a genuine drive-root argument," and suggested that fixing the extraction here would close that route as a side effect. That reads as though the drive-root rule itself is behaving and only extraction needs work. It understates the problem.
I have since tested a path with no spaces in it at all —
D:\zz-claude-guard-probe-nonexistent, nonexistent, direct child of a drive root — and it is still refused:No truncation is involved, so the two defects are independent, and fixing the target extraction described in this issue will not fix #78513. Full repro and detail: https://github.com/anthropics/claude-code/issues/78513#issuecomment-5291688505
The rest of my earlier comment stands — shape 4 (target captured from a preceding
;-separated statement) and the doubled-quote tell are unaffected by this correction. If anything the quoting detail sharpens: the no-spaces case renders doubled quotes at both ends (''D:\zz-...''), while the spaced case shows a doubled quote only at the front (''D:\some') because the truncation discards the trailing one.