Sandbox auto-adds config/ to denyWithinAllow, breaking git checkout across refs
Summary
Claude Code's sandbox auto-includes config/ (the Rails-conventional config
directory) in its denyWithinAllow list when the working directory is a Rails
project. There is no corresponding user setting — .claude/settings.json,.claude/settings.local.json, and ~/.claude/settings.json contain no rule
that would produce this entry.
This is the same shape of bug as #40133 (.claude/skills auto-added) and
#51303 (.vscode/ hardcoded deny), but for a directory that is part of normal
application source code, not editor/Claude metadata.
Repro
- In project with
config/, start a Claude Code session with the sandbox enabled. - Inspect the
Bashtool's sandbox block in the system prompt —config/
appears in denyWithinAllow despite no user config asking for it.
- Have Claude run
git checkout <other-ref>where<other-ref>differs in
any file under config/.
Expected
git checkout succeeds; the working tree matches the target ref.
Actual
git checkout partially succeeds — HEAD moves to the target ref, but the
sandbox blocks the unlink/replace of files inside config/:
warning: unable to unlink 'config/<file>': Operation not permitted
error: unable to unlink old 'config/application.rb': Operation not permitted
error: unable to unlink old 'config/database.yml': Operation not permitted
error: unable to unlink old 'config/routes.rb': Operation not permitted
...
The working tree is left in a hybrid state: target ref's commit pointer, but
the previous ref's config/* files still on disk. Returning to the original
ref then fails because the tree is dirty:
error: Your local changes to the following files would be overwritten by checkout:
config/application.rb
config/database.yml
...
Impact
This silently corrupts any workflow that uses git checkout to switch refs —
e.g. baseline-vs-HEAD performance benchmarking, bisecting, or comparing
behavior across branches. Measurements taken after the partial checkout are
invalid because they run against a hybrid tree, but neither Claude nor the
user gets a clear error at the point the corruption happens.
Environment
- Platform: macOS (sandbox-exec, not bwrap)
Asks
- Document where the
denyWithinAllowlist comes from — currently undocumented
(the published settings docs cover allowWrite/denyWrite/denyRead/
allowRead only).
- Either don't auto-add
config/(standard for Rails projects), or makegit checkout
(and similar tree-mutating git operations) bypass the sandbox by default.
- At minimum, fail loudly when a
gitcommand can't fully apply a tree
change because of sandbox restrictions, instead of leaving the working
tree silently corrupted.
Related
- #40133 —
.claude/skillsauto-added todenyWithinAllow - #51303 — Sandbox blocks
git checkout/worktree addon repos tracking
.vscode/ (closed as duplicate of #46000)
- #46000 — Sandbox filesystem rules can't scope to git root
Showing cached comments. Read the full discussion on GitHub ↗
7 Comments
Found 3 possible duplicate issues:
This issue will be automatically closed as a duplicate in 3 days.
🤖 Generated with Claude Code
I'm running into the same thing; I have various projects with a 'config' directory at their top-level (containing my own files; not claude stuff) and they wind up as denyWithinAllow.
This behaviour affecting the
/configdirectory in particular is less than ideal as it conflicts with a commonly used directory that _should_ be modifiable by the sandbox. It appears to be a rule targeted at the.gitdirectory that is insufficiently scoped.I like the idea of secure defaults, and I wonder if the current behaviour would be more acceptable if it could be overridden. Overall though, I need the product to take a more deliberate approach to its defaults.
The sandbox is a tradeoff between safety and UX friction. This default means that the user's choice is:
This issue is definitely active and affecting current versions of Claude.
I just bumped in to this after setting up sandbox in my Rails app.
This is still happening in Rails apps
Same thing on
2.1.220, macOS 26.5.2 arm64, in a Rails app. Thegit reset --hard <other-ref>variant fails the same way asgit checkoutand is probably worth adding to the repro: it aborts partway withfatal: cannot create directory at 'config/locales/views/viewer': Operation not permitted, leavesHEADwhere it was, and leaves 83 files already updated to the target ref. So you get the hybrid tree with nothing in the output suggesting most of it applied. Recovering needsgit reset --hard HEADplusgit clean -fd, and thecleanis the dangerous part if there were untracked files worth keeping.Poking at the bundled binary, this set looks like where it comes from:
head,objects,refs,hooksandconfigare.gitinternals flattened into a set of bare path segment names, so a project's top-levelconfig/matches a rule meant for.git/config. Matches the guess in the comment above.Measured behaviour here, in case it helps scope a fix: only the project root
config/is denied.app/config/writes fine, and aconfigdirectory in any other tree is fine..git/configand.git/hooks/are denied,.git/info/is not.On workarounds, I don't think one exists today. From the same bundle the write config is
{allowOnly, denyWithinAllow}, wheredenyWithinAllowis the user'sdenyWriteplus the auto-added paths, and theallowWithinDenyfield only exists on the read config, fed byallowRead. So no settings key can re-permit a path once it lands in the write deny list, which is what makes #46000 and #61909 closing as not-planned leave this with no user-side fix at all.Agree with the tradeoff point above, and what is left as an escape hatch is coarse in both directions:
excludedCommandsunsandboxes the entire shell invocation (#40831, #45113), and otherwise you turn the sandbox off completely. Either one gives up a lot of attack surface to get write access to a directory that is ordinary application source, so this is landing as a real disincentive to running sandboxed at all. A scoped fix from the team would be worth much more here than either workaround.Would scoping those five segments to
.git/be enough, or is that set doing double duty somewhere else?