[BUG] PowerShell guard blocks Remove-Item for any direct child of a drive root, including nonexistent paths
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet (closest: #73524, #69461, #73882 — see below)
- [x] This is a single bug report
- [x] I am using the latest version of Claude Code (reproduced on 2.1.212)
TL;DR
PS> Remove-Item -LiteralPath "C:\zzprobe1" -Recurse -Force # this path does not exist
Remove-Item on system path 'C:\zzprobe1' is blocked. This path is protected from removal.
C:\zzprobe1 is an invented, nonexistent path. Any target directly under a drive root (<Drive>:\<name>) is blocked the same way — existing or not, critical or not, with or without -Recurse — while targets two levels deep pass through. The deny is non-overridable (classifierApprovable: false per #73524), so ordinary top-level folders like C:\some-tool or D:\work can never be deleted through the PowerShell tool.
What's Wrong?
The destructive-path guard appears to classify by syntactic path depth ("is it one component under a drive root?") rather than by identity ("is it actually a critical location?"). This is not the unrelated-token mis-extraction reported in #73524 / #69461 / #73882: here the target is bound explicitly via -LiteralPath, it is the only path-like token in the command, and the error echoes it back verbatim. The binding fix proposed in #69461 would not change this case.
What Should Happen?
Deleting an ordinary user-owned path like C:\my-project should go through the normal permission flow. Only genuinely critical locations (drive roots themselves, C:\Windows, C:\Program Files, C:\ProgramData, C:\Users, /, /usr, …) should hit the hard block.
Error Messages/Logs
Every blocked case produces the same message, echoing the target verbatim:
Remove-Item on system path 'C:\zzprobe1' is blocked. This path is protected from removal.
Remove-Item on system path 'D:\zzprobe1' is blocked. This path is protected from removal.
Remove-Item on system path 'C:\zzprobe_empty' is blocked. This path is protected from removal.
Remove-Item on system path 'C:\Windows' is blocked. This path is protected from removal.
The tool call is denied before execution (toolDenialKind: "permission-rule" in the session transcript); PowerShell never runs the command. The error string exists in claude.exe and in neither pwsh.exe nor powershell.exe, and no PreToolUse hook is configured — the block happens in Claude Code before the command reaches PowerShell.
Steps to Reproduce
- On Windows, start Claude Code in a fresh directory (no project config; verify no
PreToolUsehook). - Ask Claude to run, as a single PowerShell tool call:
Remove-Item -LiteralPath "C:\zzprobe1" -Recurse -Force(the path does not exist). - Observe the tool call is denied with
Remove-Item on system path 'C:\zzprobe1' is blocked...— before PowerShell runs. - Repeat with a two-component path (
C:\zzprobe1\zzprobe2): the command now reaches PowerShell (fails there withCannot find path, as expected). - Optionally run the rest of the matrix below; results are deterministic and reproduced identically on two machines (see Environment).
Test matrix (each row a standalone tool call; paths invented/nonexistent unless noted; nothing is ever deleted):
| # | Command | Result |
|---|---|---|
| 1 | Remove-Item -LiteralPath "C:\zzprobe1" -Recurse -Force | 🚫 blocked |
| 2 | Remove-Item -LiteralPath "C:\zzprobe1\zzprobe2" -Recurse -Force | ✅ reaches PowerShell (Cannot find path ...) |
| 3 | Remove-Item -LiteralPath "D:\zzprobe1" -Recurse -Force | 🚫 blocked (not specific to C:) |
| 4 | Remove-Item -LiteralPath "C:\zzprobe1" -Force | 🚫 blocked (-Recurse not required) |
| 5 | Remove-Item -LiteralPath "C:\zzprobe_empty" — existing, empty, user-created dir, no flags | 🚫 blocked |
| 6 | Remove-Item -LiteralPath "C:\Windows\zznonexistent" -Recurse -Force | ✅ reaches PowerShell |
| 7 | Remove-Item -LiteralPath "C:\Windows" -Recurse -Force -WhatIf | 🚫 blocked (even though -WhatIf deletes nothing) |
Rows 1 vs 2 show the boundary is depth. Rows 6 vs 7 show it is only depth, not identity: C:\Windows itself is blocked, yet a deeper (nonexistent) path under it sails through — so the block on C:\Windows comes from the same depth rule, not from recognising it as critical. (Black-box observation; I can't see which internal stage does this.)
Why this isn't a duplicate of #73524 / #69461 / #73882
- Those describe the guard grabbing an unrelated token as the delete target (regex literals, commit-message text, here-string bodies, unresolved variables →
/). Here the target is correctly extracted — and still blocked. - #66549 (closed) — quoted paths with spaces truncated at the space (
"C:\Program Files\x"→C:\Program) — composes with this: the truncated token is a one-component path, which then meets this depth boundary. - I'm not claiming these can't be triaged together as one guard rework; the point is that the fixes currently proposed there don't address this case.
Workarounds (for anyone landing here)
Use only after independently verifying the target path is what you intend — these bypass the guard entirely:
[System.IO.Directory]::Delete($path, $true)/[System.IO.File]::Delete($path)— the guard keys on the cmdlet name.- The Bash tool's
rm. - Deleting contents first does not help — removing the emptied top-level folder is still blocked (row 5).
Environment
Reproduced identically on two machines:
| | Machine 1 | Machine 2 |
|---|---|---|
| Claude Code | 2.1.210 | 2.1.212 |
| OS | Windows 11 Pro (10.0.26200) | Windows 10 Pro (10.0.19045) |
| PowerShell | pwsh 7.6.3 (Core) | Windows PowerShell 5.1 |
| Notes | all rows; row 3 on a real D: | fresh dir, no project config; all rows except 3 (only has C:) |
Identical behaviour on pwsh 7 and Windows PowerShell 5.1 rules out PowerShell-edition specifics.
Claude Model
Not sure / Multiple models (Opus 4.8 and Fable 5 sessions; the guard fires before execution, so the model appears irrelevant)
Is this a regression?
I don't know. Directly reproduced on 2.1.210 and 2.1.212; matching blocks appear in my session logs since mid-June 2026 (versions not recorded).
Claude Code Version
2.1.210 (Claude Code) and 2.1.212 (Claude Code)
Platform
Anthropic API
Operating System
Windows (10 Pro and 11 Pro)
Terminal/Shell
VS Code integrated terminal (machine 1); PowerShell tool in both cases
Questions for maintainers
- Is blocking every first-level child of a drive root intended policy, or an over-broad heuristic? The permissions docs promise circuit breakers for root/home/critical system paths, but don't say every
<Drive>:\<name>is protected. - If it is intended, could this guard be made
classifierApprovableso a reviewed command can proceed?
---
Drafted with Claude Code's assistance; every repro row was executed and verified on my machines.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗