[BUG] WSL2: Sandbox creates empty files/directories in working directory via bwrap mount point leak
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
On WSL2, Claude Code creates empty files and directories in the working directory every time the sandbox runs a command (every Bash tool call). These are mount-point artifacts from bubblewrap (bwrap) that leak to the host filesystem due to a WSL2 kernel behavior difference from native Linux.
Files created
Every sandbox invocation creates these empty files/dirs in cwd:
From pC6 array:.gitconfig, .gitmodules, .bashrc, .bash_profile, .zshrc, .zprofile, .profile, .ripgreprc, .mcp.json
From cq1() array:.vscode/, .idea/, .claude/commands/, .claude/agents/
From git internal deny paths:.git/hooks/, .git/config
Root cause
The Linux sandbox builder (CD5 in the compiled source) sets up bwrap like this:
--ro-bind / /-- entire filesystem read-only in sandbox--bind <cwd> <cwd>-- cwd writable for the command--ro-bind /dev/null <cwd>/.bashrc(etc.) -- deny writes to sensitive dotfiles within the writable cwd
Step 3 is the problem. When the deny target doesn't exist, bwrap needs to create a mount point. On native Linux, this mount point exists only inside the mount namespace and is invisible to the host. On WSL2, bwrap creates a real 0-byte file (or empty directory) on the host filesystem that persists after bwrap exits.
There IS cleanup code (Z78/nq1 tracking + process.on("exit") handler), but:
- It only runs on process exit, not after each sandbox invocation
- Files are recreated on the next Bash tool call
- If the process is killed (SIGKILL, crash, Ctrl+C), cleanup never runs
Suggested fix
For WSL2 (isWslEnvironment() / vYA() is already detected), one of:
- Skip deny bind mounts for non-existent paths on WSL -- if the file doesn't exist, there's nothing to protect from being read; the concern about the sandboxed process creating the file is valid but the cure (mount point leak) is worse than the disease
- Clean up mount point artifacts immediately after each bwrap invocation rather than only on process exit
- Pre-check with
existsSyncand only apply deny mounts to paths that already exist (this is partially done but the non-existent path handling is what causes the leak)
Environment
- WSL2 (Linux 6.6.87.2-microsoft-standard-WSL2)
- bubblewrap 0.9.0
- Claude Code 2.1.47
What Should Happen?
No extraneous empty files are created.
Error Messages/Logs
Steps to Reproduce
mkdir -p /tmp/bwrap-test
# This creates a real empty file at /tmp/bwrap-test/.bashrc on the HOST:
bwrap --ro-bind / / --bind /tmp/bwrap-test /tmp/bwrap-test \
--ro-bind /dev/null /tmp/bwrap-test/.bashrc \
-- ls /tmp/bwrap-test/.bashrc
# Verify it leaked to host:
ls -la /tmp/bwrap-test/.bashrc
# Output: -r--r--r-- 1 user user 0 ... .bashrc
rm -rf /tmp/bwrap-test
To reproduce the Claude Code bug: start Claude Code in any git repo on WSL2, run any Bash command, then check git status --you'll see all the untracked files listed above.
Claude Model
None
Is this a regression?
Yes, this worked in a previous version
Last Working Version
_No response_
Claude Code Version
2.1.47
Platform
Anthropic API
Operating System
Windows
Terminal/Shell
WSL (Windows Subsystem for Linux)
Additional Information
_No response_
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗