[BUG] Sandbox kills every Bash call in worktrees at the default `.claude/worktrees/` location (bwrap EROFS creating protected-path mountpoints)

Status Closed — not planned
Reported on v2.1.227
Maintainer reply None cached
Activity 2 comments · opened Aug 11, 2026 · closed Aug 23, 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 built-in Bash sandbox enabled, a session running in a worktree that Claude Code itself created at the default location <repo>/.claude/worktrees/<name> cannot run any Bash command — every call dies while bwrap builds the sandbox, before the user command starts. Edit/Write tools still work; only sandboxed Bash is dead, which makes the worktree session unusable.

Mechanism, consistent with every experiment I ran (details in Additional Information):

  1. The sandbox write-denies protected paths of the session's checkout: <checkout>/.github, <checkout>/.gitconfig, <checkout>/.mcp.json, <checkout>/.claude/..., etc.
  2. A non-existent denyWrite path is enforced by having bwrap create the mountpoint and ro-bind over it, and this creation is fail-closed (the same asymmetry documented in #79997 — missing read-allow paths are skipped, missing write-deny mountpoints abort the sandbox).
  3. These protected paths are usually absent from a fresh worktree checkout (.gitconfig and .mcp.json are virtually always untracked; .claude/ and .github often are).
  4. The worktree lives inside <repo>/.claude, which the sandbox has already ro-bound as a protected path of the main checkout. Mountpoint creation inside the worktree therefore hits EROFS and bwrap aborts.

The permission-guard layer already exempts .claude/worktrees/ from protected-path treatment (mentioned in #80721), but the bwrap profile builder has no such exemption — so Claude Code's own default worktree location conflicts with Claude Code's own sandbox.

What Should Happen?

A session in a worktree that Claude Code created at its own default location should be able to run sandboxed Bash commands.

Either of these would fix it (both seem worth doing):

  1. Carve the session's worktree subtree out of the parent checkout's .claude RO bind, mirroring the guard-layer exemption of .claude/worktrees/, so mountpoints inside the worktree can be created.
  2. Make write-deny mountpoint creation fail-open (skip the bind when the mountpoint cannot be created under an unwritable parent), as proposed in #79997 — the kernel already denies writes there, so the skipped bind loses no protection.

Error Messages/Logs

bwrap: Can't create file at /home/<user>/tools/<repo>/.claude/worktrees/<name>/.github: Read-only file system

# After pre-creating <worktree>/.github by hand, the failure just moves to the next missing protected path:
bwrap: Can't create file at /home/<user>/tools/<repo>/.claude/worktrees/<name>/.gitconfig: Read-only file system

Steps to Reproduce

  1. Use a git repo where at least one protected path (.github, .gitconfig, .mcp.json, .claude/) is absent from a fresh checkout — i.e. nearly any repo. In my repro, .gitignore contains .claude/, .mcp.json, and .github.
  2. Enable the Bash sandbox (bwrap) and trust the workspace:

``
cd ~/some-repo
claude # sandbox enabled: sandbox.enabled=true, failIfUnavailable=true
``

  1. Start a worktree session at the default location:

``
claude --worktree test
``

  1. Ask for any Bash command in the session — or reproduce headlessly:

``
cd .claude/worktrees/test
claude -p 'Use the Bash tool to run: ls -la'
``

  1. Every Bash call fails with bwrap: Can't create file at .../.claude/worktrees/test/.github: Read-only file system.

Reproduced in two unrelated repos on the same machine. The main checkout of the same repos runs sandboxed Bash fine.

Claude Model

Not sure / Multiple models

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.227 (Claude Code)

Platform

Anthropic API

Operating System

Ubuntu/Debian Linux

Terminal/Shell

WSL (Windows Subsystem for Linux)

Additional Information

Environment: WSL2 — Ubuntu 24.04.4 LTS, kernel 6.6.87.2-microsoft-standard-WSL2, bubblewrap 0.9.0. Sandbox settings: enabled: true, failIfUnavailable: true, allowUnsandboxedCommands: false, network strictAllowlist: true.

What I ruled out:

  • Pre-creating the missing mountpoints (touch <wt>/.github <wt>/.mcp.json; mkdir <wt>/.claude): the failure just moves to the next missing protected path (.gitconfig), confirming the whole worktree subtree is read-only at profile-build time — it cannot be fixed one path at a time.
  • sandbox.filesystem.allowWrite: ["<repo>/.claude/worktrees"] in user settings: no effect; an allow entry cannot undo the ancestor's RO deny-bind.
  • A worktree outside .claude/ (git worktree add ../repo-test + claude in it): works perfectly in the same repo — Bash runs, and the protected paths are correctly masked (visible as crw-rw-rw- character devices), proving mountpoint creation succeeds as soon as the parent chain is writable.
  • .git-as-file (#22320): different failure there (Can't mkdir parents for .../.git/hooks: Not a directory); that issue is closed as duplicate and my path/errno differ.
  • WSL2 userns/AppArmor problems: not the cause; the sandbox works fine in the main checkout of the same repos.

Related issues (all distinct from this one): #79997 (same fail-closed denyWrite-mountpoint creation, triggered by root-owned ancestors instead of worktrees), #22320 (closed; .git-as-file), #83922 (unconditional .mcp.json ancestor binds), #80721 (notes the .claude/worktrees exemption at the guard layer).

View original on GitHub ↗

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