- Book: LLM Observability Pocket Guide
- Also by me: Thinking in Go (2-book series) — Complete Guide to Go Programming + Hexagonal Architecture in Go
- My project: Hermes IDE | GitHub — an IDE for developers who ship with Claude Code and other AI coding tools
- Me: xgabriel.com | GitHub
The permission was written down. "This is explicit authorization to build, correct and test them on isolated copies," the user told the agent, according to the incident write-up on nope.net. By the user's account, the copies were not isolated. Between 10:10:31 and 10:12:14 PM Eastern, a Claude Code agent deleted about 48,218 files from the live project and emptied the git object store in 103 seconds, as reported by Cyber Security News.
The AI Incident Database logged it as incident 1719, dated September 19, 2026: a sub-agent allegedly followed Windows directory junctions out of its copy. The entry cites TechRadar's coverage. Read the numbers with one caveat up front: every outlet traces back to a single account, the user's Reddit posts and the agent's own report, and the database says it "has not been independently verified."
Whatever happened on that machine, the mechanism the report describes is documented behavior: an old Windows filesystem feature and a Python function that does exactly what its docs say. You can reproduce the dangerous half in a temp folder in two minutes.
What the reports say happened
Per Cyber Security News and nope.net, which drew on the user's Reddit post, the agent's report and the attached verifier report, the chain went like this:
- According to nope.net and the AIID entry, the agent launched sub-agents. One was rebuilding a mirror of a project.
- The existing build script couldn't refresh the mirror in place, so the sub-agent wrote a Python remover for the old copy.
- The old mirror held 7,332 real files plus 614 Windows directory junctions pointing back into the live tree.
- The remover used
os.walk(..., followlinks=False), apparently assuming that flag kept it out of linked folders. The verifier foundos.path.islink()returnedFalsefor the junctions. - A guard protected only files directly at each junction's root. Everything nested below was walked and deleted.
The remover's log counted 55,550 files. Subtracting the 7,332 intended mirror files, the verifier calculated 48,218 deleted live files. The .git/objects, refs and logs directories were emptied, so git log found no commits. The index survived and still listed 7,221 paths, but the blobs behind them were gone. In a later comment, per nope.net, the user said they hoped Windows shadow copies or an off-site backup would bring it back. No outcome has been reported.
The Claude Code version, model and permission mode aren't stated in any of the reports, and neither Cyber Security News nor nope.net reports a response from Anthropic.

A junction looks like a folder inside the copy. It is a door into somewhere else.
A junction is a door, and walkers can't always see it
NTFS has three kinds of links: hard links, junctions and symbolic links. Microsoft's hard links and junctions page defines a junction as a link whose targets are directories, possibly "on different local volumes on the same computer," implemented through reparse points. Junctions can't point at a single file or at a mapped network drive. Symbolic links have rules of their own.
You make a junction with mklink /J. A directory symlink is mklink /D. Open either one and you are looking at the target's files. To a program, the question "is this a link?" gets different answers depending on which function asks.
In Python, os.path.islink() answers "is this a symbolic link?" A junction isn't one. Python 3.12 added a separate os.path.isjunction(). And os.walk, in its current source, decides whether to descend into a subdirectory with islink() or DirEntry.is_symlink(). There is no junction check.
So followlinks=False keeps os.walk out of symlinks and walks straight through junctions. This is old news on Windows: a 2014 UrBackup post described os.walk looping through junctions, and islink returning False for them.
Junctions also turn up where you didn't put them. The pnpm FAQ says that on Windows, "if the Developer Mode is off, we use junctions instead" of symlinks inside node_modules.
Which delete tools walk through the door
Some tools were fixed years ago. Others depend on the version you run, or on reports nobody has settled.
| Tool | Behavior with a junction |
|---|---|
Python os.walk(followlinks=False)
|
Descends. Uses islink(), which is False for junctions |
Python shutil.rmtree
|
Since 3.8, on Windows, removes the junction without deleting its contents (docs) |
PowerShell Remove-Item -Recurse
|
A bug about removing directory links was marked fixed in PowerShell 6. A Claude Code bug report says Windows PowerShell 5.1 still follows junctions |
Git for Windows git clean
|
Used to delete through junctions (#607). A 2019 fix stopped it traversing mount points |
cmd /c rmdir /S /Q |
Suggested in the same Claude Code report as removing junctions without following them |
In February 2026 a user asked Claude Code to remove two git worktrees from a pnpm monorepo on Windows. The issue says a recursive delete (Remove-Item -Recurse -Force, or possibly rm -rf from Git Bash) followed the junctions in node_modules and permanently deleted folders in the user's profile and source code, with Accept Edits on. It is labelled data-loss and has repro, and a bot closed it as "not planned" after a month of inactivity. Same mechanism, seven months earlier, and that time the trigger was cleaning up worktrees.
Try it: a junction in a temp folder
This runs on Windows in cmd.exe. Nothing here deletes anything until the last two lines, and those remove only the demo folder. It builds a "live" folder with one file, and a "copy" that contains a junction to it:
cd /d "%TEMP%"
mkdir jdemo\live jdemo\copy
echo keep me> jdemo\live\important.txt
mklink /J "%TEMP%\jdemo\copy\door" "%TEMP%\jdemo\live"
Now ask Python what it sees. This is a dry run. It prints, it doesn't delete:
import os
import sys
root = os.path.join(os.environ["TEMP"], "jdemo", "copy")
door = os.path.join(root, "door")
print("islink:", os.path.islink(door))
if sys.version_info >= (3, 12):
print("isjunction:", os.path.isjunction(door))
for dirpath, dirnames, filenames in os.walk(root):
for name in filenames:
print("would delete", os.path.join(dirpath, name))
Going by the docs and the source above, islink should print False and isjunction True, and the walk should offer you important.txt under door. That file lives in live. A remover built on this loop deletes it. I haven't run it on a Windows machine myself, so if your result differs, I'd like to hear about it in the comments.
The fix is a rule that holds for any kind of link: before you delete a path, resolve it and check that it is still inside the folder you meant to clean. os.path.realpath has resolved junctions on Windows since 3.8:
def inside(path, root):
real = os.path.realpath(path)
base = os.path.realpath(root)
try:
return os.path.commonpath([real, base]) == base
except ValueError: # different drive
return False
Run inside() on every file the remover is about to delete, and refuse when it returns False. A junction to another drive letter makes commonpath raise, which is why that case counts as outside. Clean up the demo by removing the junction first, then the folder:
rmdir "%TEMP%\jdemo\copy\door"
rmdir /S /Q "%TEMP%\jdemo"

Every worktree is its own branch. They all draw on one object store.
Every worktree shares one .git object store
I recommend worktrees for keeping parallel agents out of each other's files. They don't fence off the disk.
The git worktree docs say a new worktree is "sharing everything except per-worktree files such as HEAD, index, etc." Inside a linked worktree, $GIT_COMMON_DIR points "back to the main worktree's $GIT_DIR," through a .git file at the worktree's top. The objects, refs and logs this incident lost are the ones every worktree of that repository uses. Give an agent its own worktree and it gets its own branch and its own files on disk. It still holds a path to the shared object store, and nothing in git stops a process from following it.
Claude Code's own sandbox docs agree. When the working directory is a linked worktree, the Bash sandbox "also allows writes to the main repository's shared .git directory so commands such as git commit can update refs and the index." And that sandbox runs on macOS, Linux and WSL2 only: "On native Windows, Claude Code runs commands unsandboxed." Its checkpoints don't track files changed by Bash commands, and they usually don't restore sub-agent edits either.
Hermes works the same way here. I build Hermes IDE, so weigh this with that in mind. Hermes starts each task on its own hermes/<name> branch in its own worktree, and every agent runs its own interface in a real terminal, "exactly as it would anywhere else," per the README. That keeps two agents from overwriting each other's files. A junction inside that worktree still points wherever it points, and the agent's shell has your user's permissions. Since 2.3, a task can also include extra folders, and those are edited in place, on whatever branch they are on.

The ⌘N New task sheet in Hermes 2.3: main project orders-api plus two ticked extra folders, with the "Also:" chips noting extra folders are edited in place, not on the task branch.
What actually limits the blast radius, in the order I'd add it:
-
Push the agent's branch often. A commit on a remote survives a local
.gitwipe. The incident's index outlived its objects, and that didn't help. -
On Windows, run the agent inside WSL2, where Claude Code's Bash sandbox is available (it is off by default; turn it on with
/sandbox), or inside a container or VM for anything unattended. - Keep shell deletes behind an approval prompt, and check what your permission mode approves on its own. The February report had Accept Edits on.
-
Teach your own scripts the
inside()check. Agents copy patterns from your repo. - Keep an off-machine backup. It was the user's last hope here.
If this was useful
If you run several agents on one repo, Hermes IDE gives each task its own branch and worktree, so they stop overwriting each other's files. It's free, source-available under BSL 1.1, runs on macOS, Windows and Linux, and isn't affiliated with Nous Research, Anthropic or any agent vendor. If you try it, star it on GitHub or open an issue when something breaks. Notice that almost everything we know about this incident comes from the agent's own report and its delete log. If you'd rather have a record of what your agents did that doesn't depend on the agent telling you, the LLM Observability Pocket Guide walks through picking the tracing and evals tools that give you one.

Top comments (0)