[BUG] [Linux/Sandbox] bubblewrap creates persistent 0-byte read-only stub files in .claude/ of other projects

Resolved 💬 5 comments Opened Feb 24, 2026 by ikeniborn Closed Apr 1, 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?

---
Description

When the sandbox is enabled on Linux, bubblewrap creates 0-byte read-only stub files inside the .claude/ directories of other projects that are referenced during sandbox initialization.
These files persist on disk after the sandbox container exits — there is no cleanup.

What Should Happen?

---
Environment

  • Claude Code: 2.1.51
  • OS: Ubuntu 24.04.4 LTS, Linux 6.17.0 x86_64
  • bubblewrap: 0.9.0
  • @anthropic-ai/sandbox-runtime (srt): 1.0.0
  • Sandbox enabled via settings.json: { "sandbox": { "enabled": true } }

---
Steps to Reproduce

  1. Have multiple projects, e.g.:

~/projects/project-a/ ← Claude Code runs here, CLAUDE_CONFIG_DIR set here
~/projects/project-b/ ← another project with its own .claude/

  1. Enable sandbox in settings.json:

{ "sandbox": { "enabled": true } }

  1. Run any sandboxed tool (Bash, Read, Write) or a sub-agent (Task tool) that touches project-b
  2. Check project-b/.claude/:

$ ls -la ~/projects/project-b/.claude/
-r--r--r-- 1 user user 0 Feb 24 15:19 settings.json
-r--r--r-- 1 user user 0 Feb 24 15:19 settings.local.json
-r--r--r-- 1 user user 0 Feb 24 15:19 agents ← file, not directory
-r--r--r-- 1 user user 0 Feb 24 15:19 commands ← file, not directory
-r--r--r-- 1 user user 0 Feb 24 15:19 skills ← file, not directory

All files are created at the same millisecond (single atomic bubblewrap operation) and remain after the session ends.

---
Expected Behavior

  • Stub files used for namespace masking should be temporary and cleaned up when the bubblewrap container exits
  • No files should be written to filesystem paths outside the current project's working directory or CLAUDE_CONFIG_DIR

---
Actual Behavior

  • 0-byte chmod 444 files persist in .claude/ of unrelated projects
  • agents, commands, skills are created as regular files instead of directories — this prevents Claude Code from later creating these as directories in those projects (causes errors on next

open)

  • Files are owned by the current user, not recoverable without chmod first

Manual cleanup required:
find /path/to/other-project/.claude -maxdepth 1 -type f -empty -perm 444 \
-exec chmod 644 {} \; -delete

---
Root Cause (suspected)

bubblewrap uses --ro-bind /dev/null <path> to mask filesystem paths that should not be accessible inside the container. When Claude Code initializes the sandbox, it binds /dev/null to paths
like <other-project>/.claude/settings.json, agents, commands etc. The bind-mount itself is namespace-local (invisible inside the container), but the bind target file must exist on the real
filesystem — so bubblewrap creates it. After the container exits, these files are not removed.

---
Impact

  • Silent corruption of .claude/ in other projects on the same machine
  • Breaks agents/ and commands/ directories in affected projects (file blocks directory creation at same path)
  • Not visible to the user until they open the affected project

Error Messages/Logs

Steps to Reproduce

▐▛███▜▌ Claude Code v2.1.51
▝▜█████▛▘ Sonnet 4.6 · Claude API
▘▘ ▝▝ ~/Documents/Project/iclaude

❯ add type hints to test.py

● Read 1 file (ctrl+o to expand)

● Файл test.py существует, но пустой (1 пустая строка). Добавить type hints не к чему — нет кода.

Хочешь, чтобы я добавил какой-то код с type hints, или ты имел в виду другой файл?

❯ add some sample functions so I can see the hints

● Write(test.py)
⎿ Wrote 19 lines to test.py
from typing import Optional

def greet(name: str) -> str:
return f"Hello, {name}!"

def add(a: int, b: int) -> int:
return a + b

… +9 lines (ctrl+o to expand)

● Добавил четыре функции с type hints:

  • greet — str -> str
  • add — int, int -> int
  • average — list[float] -> Optional[float] (возвращает None для пустого списка)
  • find_user — dict[str, int], str -> Optional[int]

Claude Model

Sonnet (default)

Is this a regression?

Yes, this worked in a previous version

Last Working Version

_No response_

Claude Code Version

2.1.51

Platform

Other

Operating System

Ubuntu/Debian Linux

Terminal/Shell

VS Code integrated terminal

Additional Information

_No response_

View original on GitHub ↗

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