bwrap sandbox crashes on fresh project dirs: 'Can't create file at <cwd>/.claude/settings.json: Read-only file system'
Bug description
When starting a Claude Code session with a working directory that has no pre-existing .claude/settings.json, the sandbox (bwrap) crashes on the very first Bash tool call — and continues crashing on every subsequent Bash call — with:
bwrap: Can't create file at <cwd>/.claude/settings.json: Read-only file system
This makes the Bash tool (and by extension most agentic work) completely unusable in any fresh project directory, on this machine, until the issue is worked around manually.
Environment
- OS: Bazzite (Fedora Atomic / Kinoite base),
VERSION="44.20260629.0 (Kinoite)",ID_LIKE="fedora" - Kernel:
Linux bazzite 7.0.9-ogc3.2.fc44.x86_64 claude --version: 2.1.207 (Claude Code)- Install method: official
curl ... | bashinstaller (native binary, not Flatpak/toolbox/distrobox) /var/homeis a real btrfs subvolume (/dev/nvme0n1p3 on /var/home type btrfs (rw,relatime,...));/homeis a symlink to/var/home(standard on Fedora Atomic/Bazzite/Silverblue-family systems)~/.claude/settings.jsonhassandbox.enabled: true, withfilesystem.allowRead/allowWriteincluding the working directory in question (tried both the/home/...symlink form and the canonical/var/home/...form — neither avoided the crash)
Repro steps
- On a Fedora Atomic-family system where
/homeis a symlink to/var/home(or generally: any system),cdinto a directory that has never been used as a Claude Code project root (no.claude/subdirectory, or a.claude/dir missing some of its expected files). - Start
claudethere withsandbox.enabled: truein~/.claude/settings.json. - Run any Bash tool command (even a no-op like
echo test).
Observed behavior
The Bash tool fails with a bwrap filesystem error referencing a different missing file each time, in this order, as each preceding one is manually pre-created:
<cwd>/.claude/settings.json<cwd>/.claude/settings.local.json<cwd>/.gitconfig
(We stopped after 3 rounds once we had enough to report, but nothing suggests the list is exhaustive — it appears the harness is trying to create a placeholder/stub for each of a set of "protected" or "sandboxed" paths one at a time, and errors out the whole Bash tool call the moment any single one fails to be created, rather than continuing past it or creating them all up front.)
Manually pre-creating each file (empty file, or {} for JSON files) as it's named in the error message allows the next Bash call to proceed further, until it hits the next missing path. This strongly suggests the crash is caused by the harness failing to touch/create these placeholder files — not by an actual read-only filesystem (see below), and not by genuine deny-list enforcement (creating the files unblocks progress, it doesn't get rejected outright).
What we ruled out
- Not a real read-only filesystem or permissions issue:
mount/findmnt/staton the target directory (run directly in the user's shell, outside the sandbox) confirm it's a normalrwbtrfs mount, owned by the invoking user,0755permissions. - Not a
/homevs/var/homesymlink path-mismatch in user config: updating~/.claude/settings.json'ssandbox.filesystem.allowRead/allowWriteto use the canonical/var/home/...form (matching the session's actual resolved cwd) had no effect — the crash was identical before and after. - Not a nested container/Flatpak/toolbox sandboxing layer: Claude Code was installed via the official native
curl | bashinstaller, running directly on the host with no additional container layer to explain a divergent filesystem view. - Not fixable via
~/.claude/settings.jsonat all: no combination ofsandbox.filesystem.allowRead/allowWritechanges resolved it. Settingsandbox.enabled: falseis the only workaround found, which disables the OS-level sandbox entirely (losing the bwrap-level enforcement backstop, leaving only the permission-prompt layer).
Impact
Because the specific list of paths the harness wants to pre-create isn't documented (we only discovered each one by hitting the crash, fixing it, and hitting the next), it's impractical to pre-populate a directory before first use — e.g., a freshly-cloned repository the user hasn't opened Claude Code in yet will hit this immediately, with no way to know in advance which files need to be manually created. On this machine, the only viable workaround currently is disabling the sandbox entirely (sandbox.enabled: false), which removes OS-level filesystem/network confinement for all Bash tool calls.
Suggested fix directions
- The placeholder-creation step for self-protected config paths should tolerate/ignore failures to create a not-yet-existing file (e.g., skip the read-only bind-mount for that specific path if it doesn't exist yet, rather than crashing the entire sandbox setup), OR
- All placeholder paths should be created/ensured in a single batch before the sandbox mounts are constructed, with clear, actionable error output if any single one fails (rather than surfacing as a bare
bwraperror that gives no indication it's an internal harness bootstrap issue), OR - Investigate why file creation is failing for a target that is, at the real filesystem level, fully writable — since none of the ruled-out causes above explain the actual EROFS being raised inside the bwrap-constructed mount namespace.