[BUG] Cowork: mount serves null bytes for rename target after recreating rename-source path — git unusable on mounted folders
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?
Summary
When a file is created at a path that was just used as the source of a rename, the Cowork sandbox mount serves null bytes (correct size, all \x00) for the rename's target — via both read() and mmap. The cache appears to be path-keyed, not inode-keyed: recreating the rename-source path rebinds the renamed file's cached view to the new (empty) file.
Git's lockfile discipline (config.lock → rename → config, repeated per update) hits this sequence on every consecutive config/index/ref write, so git init fails deterministically on any mounted folder with fatal: bad config line 1 in file .../.git/config (the parser reads 36 null bytes). The file on the host disk is correct throughout — only the sandbox view is corrupted. Read-modify-write from the sandbox would propagate the corruption to host disk.
Distinction from known issues
- #42520 (FUSE cache coherency on host-side writes): requires a host-side writer (MCP). Here all operations are sandbox-bash-originated, single session.
- #40264 (truncation after external edit, cross-session): requires external modification and a session boundary. Neither involved here. (Its "cache keyed by filename" observation matches this defect's behaviour.)
- #55877 (Edit/Write tools vs bash, rclone cache): requires the host-side file tools. No file tool is involved in this repro.
This is sandbox-only, single-session, deterministic, with a 3-step syscall repro.
Bisection results (each cycle: write lock → rename → condition → read target)
| Variant | Result |
| --- | --- |
| rename → read, nothing else | OK |
| rename → prior ENOENT probe of target | OK |
| rename → create unrelated file in same dir → read | OK |
| rename → recreate file at rename-source path → mmap target | NULLS (36/36) |
| rename → recreate file at rename-source path → read() target | NULLS (36/36) |
| same sequence on sandbox-native /tmp (control) | OK |
Reproduced 5/5 across repeated cycles. Also reproduced with git init directly: 3/3 failures on two different mounts (one Google Drive mirror, one plain local NTFS folder — defect is mount-stack-wide, not Drive-related).
strace excerpt (git init)
openat(AT_FDCWD, ".../.git/config", O_RDONLY) = -1 ENOENT
openat(AT_FDCWD, ".../.git/config.lock", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 3
rename(".../.git/config.lock", ".../.git/config") = 0
openat(AT_FDCWD, ".../.git/config.lock", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 3 <- rebind trigger
openat(AT_FDCWD, ".../.git/config", O_RDONLY) = 4 <- open succeeds, read returns nulls
=> fatal: bad config line 1 in file .../.git/config
A secondary symptom: after the failed init, ls .git/ listed config (size 54) while open()/stat of the same path returned ENOENT — directory-entry vs lookup divergence in the same cache layer.
Impact
- git is unusable on mounted folders (init, and by extension every index/ref/config update).
- Any tool using the lockfile-rename idiom (atomic-save editors, databases, package managers) risks silent null-byte reads and, via read-modify-write, real host-disk corruption.
Environment
- Platform: Windows 11, Claude desktop app (Cowork mode)
- Mount stack: NTFS → virtiofs → bindfs (FUSE) → sandbox
- Reproduced on: Google Drive mirror folder AND plain local folder (identical behaviour)
What Should Happen?
Creating a file at a former rename-source path must not affect the cached view of the rename target. Cache entries should be keyed/invalidated by inode, not path. git init should succeed on a mounted workspace folder.
Error Messages/Logs
fatal: bad config line 1 in file /sessions/<session>/mnt/<folder>/.git/config
(no error from the filesystem itself — reads silently return null bytes; secondary symptom: ls lists .git/config while open()/stat return ENOENT)
Steps to Reproduce
Minimal reproduction (no git required) — run inside the sandbox on any mounted workspace folder:
import os
d = "/sessions/<session>/mnt/<folder>/repro"
os.makedirs(d, exist_ok=True)
target, lock = f"{d}/config", f"{d}/config.lock"
content = b"[core]\n\trepositoryformatversion = 0\n"
# 1. create lock and write content
fd = os.open(lock, os.O_RDWR | os.O_CREAT | os.O_EXCL, 0o666)
os.write(fd, content); os.close(fd)
# 2. rename lock -> target (git's atomic-update pattern)
os.rename(lock, target)
# 3. create a NEW file at the rename-source path (git does this for the next update)
fd2 = os.open(lock, os.O_RDWR | os.O_CREAT | os.O_EXCL, 0o666)
# 4. read target -> returns correct size but all null bytes
with open(target, "rb") as f:
data = f.read()
print(len(data), data) # 36 b'\x00\x00...' EXPECTED: the [core] content
os.close(fd2)
Or simply: open a Cowork session with any folder mounted and run git init in it from bash — fails deterministically with fatal: bad config line 1.
Claude Model
Not sure / Multiple models
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
N/A — Cowork (Claude desktop app), sandbox environment
Platform
Anthropic API
Operating System
Windows
Terminal/Shell
Other
Additional Information
_No response_
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗