Sandbox auto-adds config/ to denyWithinAllow, breaking git checkout across refs

Status Open
Maintainer reply None cached
Activity 8 comments · opened May 5, 2026

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

  1. In project with config/, start a Claude Code session with the sandbox enabled.
  2. Inspect the Bash tool's sandbox block in the system prompt — config/

appears in denyWithinAllow despite no user config asking for it.

  1. 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

  1. Document where the denyWithinAllow list comes from — currently undocumented

(the published settings docs cover allowWrite/denyWrite/denyRead/
allowRead only).

  1. Either don't auto-add config/ (standard for Rails projects), or make git checkout

(and similar tree-mutating git operations) bypass the sandbox by default.

  1. At minimum, fail loudly when a git command can't fully apply a tree

change because of sandbox restrictions, instead of leaving the working
tree silently corrupted.

Related

  • #40133 — .claude/skills auto-added to denyWithinAllow
  • #51303 — Sandbox blocks git checkout/worktree add on repos tracking

.vscode/ (closed as duplicate of #46000)

  • #46000 — Sandbox filesystem rules can't scope to git root

View original on GitHub ↗

7 Comments

github-actions[bot] · 3 months ago

Found 3 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/53891
  2. https://github.com/anthropics/claude-code/issues/51303
  3. https://github.com/anthropics/claude-code/issues/40133

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

efroemling · 3 months ago

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.

jamestelfer · 3 months ago

This behaviour affecting the /config directory 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 .git directory 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:

  • turn off the overall sandbox protections and a lot of "always allow" bash rules. This reveals a far wider attack surface by default
  • use the sandbox and find that while there are fewer prompts, the friction when issues occur is more significant and requires more effort to work around.
jamestelfer · 3 months ago

This issue is definitely active and affecting current versions of Claude.

andypearson · 2 months ago

I just bumped in to this after setting up sandbox in my Rails app.

connorshea · 1 month ago

This is still happening in Rails apps

pulkit110 · 1 month ago

Same thing on 2.1.220, macOS 26.5.2 arm64, in a Rails app. The git reset --hard <other-ref> variant fails the same way as git checkout and is probably worth adding to the repro: it aborts partway with fatal: cannot create directory at 'config/locales/views/viewer': Operation not permitted, leaves HEAD where 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 needs git reset --hard HEAD plus git clean -fd, and the clean is the dangerous part if there were untracked files worth keeping.

Poking at the bundled binary, this set looks like where it comes from:

new Set([".git","hooks",".husky",".githooks","node_modules",".vscode",".idea",
         "head","config","objects","refs",".claude","skills","commands",
         "agents",".cargo",".devcontainer",".yarn",".mvn"])

head, objects, refs, hooks and config are .git internals flattened into a set of bare path segment names, so a project's top-level config/ 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 a config directory in any other tree is fine. .git/config and .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}, where denyWithinAllow is the user's denyWrite plus the auto-added paths, and the allowWithinDeny field only exists on the read config, fed by allowRead. 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: excludedCommands unsandboxes 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?

Showing cached comments. Read the full discussion on GitHub ↗