Sandbox stubs break `git add .` and are indistinguishable from real repo state to the agent
Summary
With the sandbox enabled, Claude Code mounts /dev/null over protected paths that don't exist in the project root (.zshrc, .mcp.json, .claude/hooks, …). Two consequences hit the agent, not just the user's git status:
git add .fails outright inside the sandbox — it doesn't skip the stubs, it aborts with exit 128 and stages nothing, including legitimate files.git statusreports ~19 phantom untracked files that the model has no way to identify as sandbox artifacts, so it reasons about them as real repository state.
The mounting mechanism is tracked in anthropic-experimental/sandbox-runtime#139 (open since February). This issue is about the Claude Code side: the agent is given no signal, and the behaviour is undocumented. Full cross-side measurements are in my comment there: https://github.com/anthropic-experimental/sandbox-runtime/issues/139#issuecomment-5000362125
Not a duplicate of #29316. That one is the worktree variant (.git is a file → 0-byte stubs land in the project root, breaking git rev-list HEAD with "ambiguous argument"), reported fixed in 2.1.78. This reproduces in a regular clone on 2.1.212, and the artifacts are character devices rather than 0-byte files. #29316 is locked, and kosei-w90607's comment there reporting exactly this was told to open a separate issue — this is that issue.
Reproduction
Environment: Claude Code 2.1.212, Linux, bubblewrap, sandbox enabled in auto-allow mode, ordinary git clone (no worktree), no user denyRead rules.
- Launch Claude Code from a git repository whose root has no
.zshrc/.mcp.json/.claude/hooks. - Ask Claude to run
git status --short. Observe ~19 untracked paths that do not exist outside the sandbox. - Ask Claude to run
git add -n .:
error: .bash_profile: can only add regular files, symbolic links or git-directories
fatal: adding files failed
(exit 128)
- Ask Claude to run
ls -la .zshrc→crw-rw-rw- 1 nobody nobody 1, 3(a/dev/nullcharacter device).
Scope, measured: the stub set exists in the launch directory only — zero stubs in a permissions.additionalDirectories path (writable, git commit works there) and zero in a nested repo inside the launch tree. A stub appears only where a protected path is missing; existing paths are --ro-binded and leave no artifact.
Expected behaviour
git add .works in a sandboxed repository.- The agent can tell an environment artifact from real repository state.
Why this is worth fixing on the Claude Code side
Sandboxing is a good feature and I run it permanently. But its artifacts are invisible as artifacts to the model working inside them. git status is one of the most common things an agent runs, and there is nothing in the environment saying "these 19 paths are mine, ignore them". Observed over a single session:
- The agent's natural "fix" damages the user's repo. Faced with untracked shell config, the obvious suggestion is "shall I add these to
.gitignore?" — writing a permanent list of one namespace's mount points into the repo. For public dotfiles repos that publishes noise about a sandbox nobody else can see. - Every session re-derives the same explanation, at real time and token cost, because nothing carries it across sessions.
- The model documents falsehoods confidently. My own notes accumulated three claims that later measurement refuted ("they don't exist outside the mount namespace", "every writable repo gets stubs", "
git add .skips them"). Each was plausible from one vantage point; all were wrong. - A dangerous inversion in dotfiles repos, where
.zshrc/.bashrc/.gitconfigare the tracked product. An agent taught to dismiss those filenames as sandbox noise will wave away real changes to them. git add .breaking is a plain functional regression for any agent workflow that stages before committing. The workaround (always stage explicit paths) is only discoverable by hitting the error.
None of it is documented: https://code.claude.com/docs/en/sandboxing never mentions stub files, protected-path artifacts, or the git status noise — so every user and every agent rediscovers it.
Suggested fixes, in order of preference
- Don't materialize mount targets inside the working tree (the root fix; sandbox-runtime#139).
- Tell the model. Expose the stub paths to sandboxed commands in a documented, machine-readable form — an env var, or a system-reminder Claude Code already knows how to inject. This is cheap and removes the entire class of surprise even if the mounts stay.
- Hide them from git inside the namespace — e.g. append the stub paths to
.git/info/excludewithin the sandbox (local, untracked, vanishes with the mount) rather than leaving users to gitignore mount points. This would also restoregit add .. - Document the behaviour on the sandboxing page, including that the same stub is a character device inside the sandbox and a 0-byte regular file on the host — the confusion that has kept sandbox-runtime#139's two camps talking past each other for five months.
Showing cached comments. Read the full discussion on GitHub ↗
4 Comments
From my perspective, this is kind of a ridiculous problem. Factors:
jjuiopen in a dropdown terminal.jjuiis frequently triggering snapshots of my working copyThese three factors can ostensibly create the following scenario:
jjuito reword a commit (orjj, it wouldn't matter really)jj's timeline the creation of a snapshot containing the stubs and the commit reword overlap as actions.Now this is one specific example of a symptom - the underlying issue is assuming it's okay to touch the working tree because "the user won't notice". Claude Code goes to great efforts to keep stuff _out_ of the working tree in a lot of other cases (see your sprawling
~/.claudedirectory) so this, to me, is a real anomaly.I agree with the order of preference in suggestions except documentation is a must unless the root cause is completely eradicated as per option
1.. Otherwise we're playing whack-a-mole and some other user process (rather than Git/JJ) will get bitten by these random changes in the workspace.(Really any effects that are _visible_ to a user should be documented when we're talking about dev tools, which is not _remotely_ true for Claude Code, but I digress)
Adding a comment to keep this bug report alive. This has been a frustrating, persistent problem for ~6 months now.
As mentioned in https://github.com/anthropic-experimental/sandbox-runtime/issues/139#issuecomment-5145688295 this was originally reported in https://github.com/anthropics/claude-code/issues/17087 which was subsequently closed in spite of 69 thumbs-up and 26 comments.
The issue https://github.com/anthropics/claude-code/issues/17087#event-22575322475 was closed by a human github account not a github bot account, following a comment that apparently the problem disappeared for someone, and that issue was not reopened despite comments saying the problem is still present.