You enable sandbox.enabled: true because it sounds like the responsible thing
to do — a safety layer around what Claude Code can touch. Then a refs/
directory you staged at your project root disappears. Then objects/. You didn't
delete them. No tool call did. There's no error.
A report filed today explains why, and I was able to do something better than take it on faith: I found the code that does the deleting inside my own installed binary.
What the report says
#81526: with the
sandbox enabled on macOS, Claude Code recursively deletes refs/ and objects/
(contents and all) and the HEAD file from your project root after any
sandboxed Bash command — including an echo that touches nothing. The project
doesn't even need to be a git repository. The deletion is done by Claude Code's
own process, outside the tool-call lifecycle, so no PreToolUse hook can
intercept it.
The catch that makes it look random: a directory that already exists when the sandbox config is first built is only write-denied — safe. A directory created after that point is deleted. Same directory, same command, opposite outcome, depending only on when it came into being.
The report is open, has one comment, and isn't confirmed by Anthropic. It's also worth knowing it was investigated and written by Claude Code itself (Opus 5) in an agent session and filed by the user. So I checked its central claim directly.
I found the code in my own binary
The report cites specific decompiled code. My installed Claude Code 2.1.220 is a 257MB Mach-O binary with the JavaScript bundled inside it, so I grepped it. Every piece the report describes is there, verbatim.
The sandbox profile builder walks your project root looking for three names —
["HEAD","objects","refs"] — and for each one that is missing, on macOS, it
records the path into a list called Wnr. Then, after every sandboxed command,
this runs:
function NGg(){
for (let e of Wnr){
let t = e.slice(e.lastIndexOf(js.sep)+1);
try {
yp.rmSync(e, { recursive: t !== "HEAD" && t !== ".git" });
w(`[Sandbox] scrubbed planted bare-repo file: ${e}`);
} catch {}
}
}
That's copied straight out of my binary, not the issue. rmSync with
recursive: true is a delete-the-whole-tree call — and refs and objects get
true, so they go with their contents. HEAD and .git get false, which is
why a HEAD file dies but a real .git directory survives (a non-recursive
delete on a non-empty directory throws, and the catch {} swallows it). That
matches the report's scope table exactly.
So this isn't "a user says." The mechanism is in the shipping code, and the log
line it prints when it deletes something — [Sandbox] scrubbed planted bare-repo file — is sitting right there in the binary you're running.
To be precise about what I did and didn't verify: I confirmed the code path. I
did not run the live deletion — that would mean enabling the sandbox and staging
real directories to watch them die. The sandbox isn't even enabled on my machine
(I checked — no sandbox key in my settings). The binary is the evidence.
Why this matters more than it looks
The intent behind that code is innocent — it's cleanup for stub files that used
to pollute git status. But the implementation records absent paths for
deletion and then deletes anything that later appears at those names. So any
workflow that legitimately creates a refs/, objects/, or HEAD at your
project root mid-session — git bundles, bare-repo staging, an artifact or
test-fixture directory that happens to use those names — gets silently reaped.
And it's the sandbox doing it. The feature you turned on to be careful. That's the part worth sitting with: on 2.1.220 this is a safety feature with a file-deletion bug, and it has company — #78253 is a separate sandbox bug that breaks Bash entirely in git repos with 148+ files. "Enable the sandbox for safety" is not unqualified good advice right now.
What to do about it
-
If you run the sandbox on macOS on 2.1.220, don't stage
refs/,objects/, or aHEADfile at your project root mid-session. If your workflow needs those names there — bundles, bare-repo work — either turn the sandbox off or move that work out of the project root. -
Pre-create the directories before your first sandboxed command. A path that already exists when the sandbox config is built is only write-denied, not deleted. This is a stopgap, not a fix — it depends on session timing.
-
Don't count on a hook to catch it. The delete runs outside the tool-call lifecycle.
PreToolUsenever fires. Your usual guardrail isn't in the path. -
Grep your logs for the tell.
[Sandbox] scrubbed planted bare-repo file:is the exact line the code prints when it deletes one. If it's in your session output, that's what happened to your directory. -
Check whether you even have the sandbox on.
grep sandbox ~/.claude/settings.json. If it's not enabled, this particular bug can't touch you — which, on 2.1.220, is one point in favor of leaving it off until this is fixed.
What not to do
Don't read this as "Claude Code deletes your repo." The scope is specific:
project-root refs//objects//HEAD, only when created after the sandbox config
is built, with the sandbox enabled, on macOS. A real .git directory survives.
Overstating it just gets the real, narrow, verifiable problem waved off.
And don't confuse this with the model doing something — this is the harness, not Opus 5 or any model, making a filesystem decision no agent asked for. It's a different class of bug from "the model ran a bad command," and the fix lives in Claude Code, not in how you prompt.
Receipts
- #81526 — sandbox
deletes project-root
refs//objects//HEAD. Open, 1 comment, unconfirmed, filed 2026-07-27, macOS, 2.1.220. Report authored by Claude Code (Opus 5), filed by the user. - The code — the
["HEAD","objects","refs"]target list, the macOS gate, and theNGg()delete function with its[Sandbox] scrubbed planted bare-repo filelog — I read out of my own installed 2.1.220 binary on 2026-07-27. The behavioral scope and the created-after-vs-before timing are the reporter's experiments.
If Anthropic responds on the issue, I'll update this post.
The thing I keep coming back to: I only trusted this because I could open the binary and see the delete call myself. When a "safety" feature is the one removing your files, the report deserves exactly that level of checking — and this one held up.
Watch instead
This one's also a 60-second video on TikTok →
More fixes like this, before they hit the blog