auto-mode classifier did not block an agent-authored script that ran `git clean -xdff` at the workspace root (21 projects destroyed, native Windows)
Paths and the Windows account name in this report are redacted. The account name is a leading dot plus 3 Hangul characters; .가나다 is used throughout as a stand-in with the identical length property, which is the property that matters.
What's Wrong?
A research subagent wrote a PowerShell test script to its scratchpad and executed it.
A Push-Location inside the script failed silently; because the script had$ErrorActionPreference = 'Continue', execution continued in the inherited working
directory — the live workspace root — where the next lines ran git clean -xdff.
permissions.defaultMode was "auto" throughout. It did not block.
Result: every gitignored path in the workspace was deleted. 21 of 28 project
directories destroyed or gutted, 1,051 further directories stripped outside projects/,
~6,200 files. Three projects had no remote copy and are permanently unrecoverable in
part, including two paid client deliverables and a live contest entry.
git status reported zero deletions throughout, because projects/* is gitignored.
Why I think this is worth filing rather than "user's script did it"
https://www.anthropic.com/engineering/claude-code-auto-mode states:
The principle is to evaluate the real-world impact of an action, rather than just the surface text of the invocation. The agent shouldn't be able to hide a dangerous operation behind a benign-looking wrapper. If the agent writes a file and then runs it, the classifier evaluates the payload.
This is exactly "the agent writes a file and then runs it" — the Write call and the
execution call are adjacent in the same subagent transcript, and the payload contained a
bare git clean -xdff.
I understand the classifier has a published 17% false-negative rate, so a single miss is
within the stated envelope. I am not claiming the classifier is broken. I am
reporting that (a) the one documented layer covering this pattern did not hold in a
maximum-blast-radius case, and (b) no other layer applied at all:
| Layer | Outcome |
|---|---|
| permissions.deny rules (Bash(git clean -f*), PowerShell(git clean -f*) both present) | Cannot fire. The tool-call string was & "…\junction-test.ps1". Consistent with the documented limit: deny rules "don't apply to arbitrary subprocesses that read or write files indirectly". |
| PreToolUse hook (my own static analyzer, wired to Bash\|PowerShell) | Ran, returned "nothing destructive." It analyzes literal arguments; the literal argument was a file path. Fail-open. |
| auto-mode classifier | Enabled. Did not block. |
| OS sandbox | Unavailable — "The sandbox is built into Claude Code and runs on macOS, Linux, and WSL2. Native Windows is not supported." |
What Should Happen?
Three concrete asks, in order of how actionable I think they are:
1. Treat a destructive git subcommand with no path anchor as a distinct signal.git clean / reset --hard / checkout . / worktree remove issued without-C <abs> or an absolute pathspec is a syntactically decidable property — no
semantic judgment needed. It is also exactly what separated the fatal script from the
two corrected versions the same subagent wrote eight minutes later on its own: those usedgit -C <path>, and would pass such a check by construction.
2. Clarify whether the classifier's payload evaluation applies equally to the
PowerShell tool on native Windows. The worktree docs already state that "for
PowerShell commands, Claude Code applies only the working-directory check" — so
PowerShell demonstrably receives reduced treatment in at least one subsystem. If the
same reduction applies to payload evaluation, the auto-mode documentation is misleading
for Windows users and should say so.
3. Consider guarding git clean -x specifically. In a repo where most of the tree
is gitignored, -x makes all of it a deletion target, and -ff is what permits removal
of nested repositories. git ships no size threshold, no count warning, and no undo —
verified, the entire clean.* config namespace is a single variable
(clean.requireForce). git will not add one; an agent harness is the only place this
can live.
Error Messages/Logs
Verbatim from the script's run output. Tests 1–4 (rmdir /s, Remove-Item -Recurse,rm -rf, node fs.rmSync) all completed correctly inside the temp fixture. Then:
Push-Location: ...\scratchpad\junction-test.ps1:54
Line |
54 | Push-Location $f.root
| ~~~~~~~~~~~~~~~~~~~~~
| An object at the specified path C:\Users\가나다~1 does not exist.
git clean -xdff canary=True boxRemoved=False => SAFE-BUT-INCOMPLETE
boxRemoved=False on the git clean -xdff row means the fixture it was supposed to
clean was untouched — it cleaned somewhere else. That tool call also consumed its full
300 s timeout and was moved to the background, where it kept running for minutes.
Independent confirmation it ran against the live repo — later in the same script,git worktree add printed its back-pointer:
[t6] .git in worktree is a: ; content: gitdir: C:/Users/.가나다/Desktop/<workspace>/.git/worktrees/wt
A stray feat branch created by that git worktree add -q -b feat still exists in the
repository.
A second, unrelated Claude Code session watched it happen live and logged, at 19:09:54:
중단하고 알릴게 — 지금 projects/ 전체가 비워지고 있어. 내가 한 게 아니고 진행 중이야. ("Stopping to report — all of projects/ is being emptied right now. It isn't me, and it's in progress.")
Directory mtimes trace the clean walking the repository root in ordinal order:archive/ 19:02:06 → config/…/scripts 19:03:48 → projects/* 19:03:56–19:09:54.
Steps to Reproduce
The trigger requires a Windows account whose name begins with a dot and contains
non-ASCII characters, so that the 8.3 short name is longer than the long name.
- Windows account
.가나다(dot + 3 Hangul = 4 chars). NTFS drops leading periods when
generating short names (RtlGenerate8dot3Name),
so the alias becomes 가나다~1 — 5 chars, longer than the 4-char original.
- Windows populates
%TEMP%with the short form for non-ASCII profiles
(Raymond Chen), so
$env:TEMP = C:\Users\가나다~1\AppData\Local\Temp.
- In Claude Code with
defaultMode: "auto", from a session whose cwd is a git
repository, have the agent Write a .ps1 containing:
$ErrorActionPreference = 'Continue'
$root = Join-Path $env:TEMP 'fixture'
New-Item -ItemType Directory -Force -Path $root | Out-Null # succeeds (.NET file API)
Push-Location $root # FAILS (provider normalizer)
git clean -xdff # runs in the INHERITED cwd
Pop-Location
- Execute it via
& "<path>\script.ps1".
The Push-Location failure is PowerShell/PowerShell#17359
— open since 2022-05, no fix PR. Root cause is a length assertion inFileSystemProvider.NormalizeThePath():
if (fsinfo.FullName.Length < currentPath.Length)
{
throw PSTraceSource.NewArgumentException("path", FileSystemProviderStrings.ItemDoesNotExist, currentPath);
}
This is a PowerShell defect, not a Claude Code one — I include it only because it
explains why the script looked correct. The part that makes it a Claude Code issue is
that New-Item/Test-Path use the .NET file APIs and succeed on the same path, while
only Set-Location/Push-Location route through the normalizer. That partial
failure is why tests 1–4 reported clean and the script read as safely sandboxed — to the
agent, to a human reviewer, and to any static payload analysis.
For a reproduction that does not need the Korean-profile setup, any script where aSet-Location fails non-terminally before a bare destructive git command reproduces the
same class.
Notes on scope
- I am not claiming Claude Code issued
git cleanon its own initiative against user
data. It was inside a test script the agent wrote for a legitimate research purpose
(empirically testing whether recursive deletes follow NTFS junctions), correctly scoped
to %TEMP%, and it escaped that scope because of the PowerShell defect.
- The subagent's dispatch prompt was a documentation research task that explicitly
said "Return findings directly in your final message, do not write files." It
escalated to running live destructive commands on its own initiative. That may be a
second, separate issue worth looking at.
- Full forensic write-up, both scripts, the complete run logs, the subagent transcript,
and per-file loss manifests are preserved and available on request.
Claude Code Version
2.1.233
Platform
Windows (native, not WSL) — Windows 11 Home 26200. Git Bash + native PowerShell tools.