[BUG] Sandbox git-internals write-protection resolves paths against the worktree root, not `.git/` — collaterally blocks a project's top-level `config/` dir

Resolved 💬 3 comments Opened Jun 8, 2026 by alvincrespo Closed Jul 15, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

With the sandbox enabled, Claude Code adds write-deny rules to protect the repo's git internals. On a normal repository (.git is a directory, not a worktree pointer), those deny paths are built
against the worktree root instead of the resolved git directory — the .git/ segment is dropped.

The effective policy ends up with these write-deny entries:

``
/Users/me/repos/myproject/HEAD
/Users/me/repos/myproject/objects
/Users/me/repos/myproject/refs
/Users/me/repos/myproject/config
``

These are meant to be <repo>/.git/HEAD, <repo>/.git/objects, <repo>/.git/refs, <repo>/.git/config. Because the .git/ prefix is missing, the entries resolve to repo-root paths, which produces
two distinct problems:

  1. The protection misses its target. HEAD, objects, and refs don't exist at the repo root, so those three entries are no-ops — the real .git/HEAD, .git/objects, and .git/refs stay writable.
  2. Collateral damage to a legitimate project directory. The repo has a top-level config/ directory (this is a Rails app — every Rails app does). The bare config entry collides with it, so the

project's own config/ becomes write-blocked even though the repo root is in allowWrite.

None of these four entries appear in any settings file (managed, project, or user). They're synthesized at runtime by the bundled sandbox-runtime and applied in-process via the macOS Seatbelt API — there's
no profile file or settings key to inspect or override.

> Note: .git/config and .git/hooks are still correctly protected by a separate, properly-resolved built-in rule, so this is not a git-config/hooks security regression. The bug is (a) the four
worktree-root entries don't protect what they intend, and (b) they break writes to same-named project directories.

What Should Happen?

Git-internal deny paths should be resolved against the actual git directory — e.g. via git rev-parse --git-common-dir / --git-dir — yielding <repo>/.git/HEAD, <repo>/.git/config,
<repo>/.git/objects, <repo>/.git/refs (or the worktree's resolved gitdir).

A top-level project directory that happens to share a name with a git internal (config/, and likewise objects/, refs/, a file named HEAD) must remain writable when the repo root is in
allowWrite.

If HEAD/objects/refs are intended to be protected, the correctly-resolved .git/* paths should actually be covered.

Error Messages/Logs

Writing anywhere under the project's `config/` directory fails with a generic OS error that gives no hint it's git/sandbox related:


$ touch config/probe
touch: config/probe: Operation not permitted


Effective sandbox policy as reported to the session (`write.denyWithinAllow`, abridged):


/Users/me/repos/myproject/HEAD       # no-op: no such path at repo root
/Users/me/repos/myproject/objects    # no-op
/Users/me/repos/myproject/refs       # no-op
/Users/me/repos/myproject/config     # collides with the project's Rails config/ dir

Steps to Reproduce

  1. macOS (Apple Silicon), Claude Code 2.1.168 (native binary install), sandbox enabled.
  2. sandbox.filesystem.allowWrite includes the repo root ("./").
  3. Use a normal git repo — .git is a directory, not a worktree file pointer. Confirm:

``
git rev-parse --git-dir # => .git
git rev-parse --git-common-dir # => .git
test -d .git && echo DIRECTORY # => DIRECTORY
``

  1. The repo has a top-level config/ directory (e.g. any Rails app).
  2. Attempt a write into it from a sandboxed Bash command:

``
touch config/probe
`
**Expected:** succeeds (repo root is writable).
**Actual:**
touch: config/probe: Operation not permitted`.

Diagnostic showing the misalignment (which git paths are actually protected vs. the deny list):

``
touch config/probe # BLOCKED <-- the bug: project dir, not git
touch .git/config # blocked (correct, separate built-in rule)
touch .git/hooks # blocked (correct, separate built-in rule)
touch .git/HEAD # ALLOWED <-- intended target left writable
touch .git/objects/probe # ALLOWED <-- intended target left writable
touch .git/refs/probe # ALLOWED <-- intended target left writable
``

Claude Model

Opus

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.168

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

iTerm2

Additional Information

  • Version: Claude Code 2.1.168, native Mach-O arm64 binary (~/.local/share/claude/versions/2.1.168).
  • Platform: macOS (Darwin 25.5.0), Apple Silicon.
  • Mechanism: sandbox applied in-process via the macOS Seatbelt API — no sandbox-exec child process and no .sb profile on disk, so the rule isn't visible in or overridable from any settings file.

$TMPDIR for the session is /tmp/claude-<uid>.

  • Confirmed not config-driven: grep across user, project, and managed settings finds none of HEAD/objects/refs/config/denyWithinAllow.
  • Related issues:
  • #29316 — same root cause (git internals resolved against the worktree root, listing HEAD, config, hooks, objects, refs), but reported for git worktrees where .git is a file pointer, with a

different symptom (0-byte stub files in the project root). This report is the non-worktree manifestation: .git is a directory, no stub files, and the visible symptom is a project config/ dir
becoming unwritable. (#29316 is currently closed.)

  • #13195 / #58178 — the separate, correctly-resolved .git/config protection and the allowGitConfig override (confirms .git/config guarding is a distinct, working code path).
  • #32287 — adjacent "deny/allow path resolves against the wrong root, silently" class of bug.
  • Impact: legitimate writes to a project's config/ (config generators, tooling, app code that writes under config/) fail with an opaque Operation not permitted and no indication the cause is the

sandbox's git protection. Concurrently, the git internals the rule was meant to protect (HEAD/objects/refs) are left writable.

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗