[BUG] Write/Edit tools create ghost files on WSL2 drvfs mounts (statx + 9P cache poisoning)
Bug description
The Write and Edit tools create "ghost files" on WSL2 when the project is on a drvfs-mounted Windows drive (/mnt/d/). After a failed write, the target file becomes inaccessible from WSL — it cannot be read, written, deleted, or overwritten via any Linux tool. Only cmd.exe /C del (bypassing 9P) can remove it. git checkout also fails with "error: unable to create file: File exists".
This is a regression. It did not occur with earlier CLI versions (Claude 4.5 era) but happens consistently with v2.1.50 (Claude 4.6 era).
Steps to reproduce
Setup: Project on a drvfs-mounted NTFS drive (/mnt/d/), Claude Code v2.1.50, WSL2 Ubuntu, kernel 5.15.153.1-microsoft-standard-WSL2.
Reproduction (100% consistent in .claude/ subdirectories):
- Use Claude Code's Write tool to create a new file in a nested
.claude/subdirectory — e.g.,.claude/commands/pm/test-ghost.md - The Write tool fails immediately with:
````
ENOENT: no such file or directory, statx '/mnt/d/Grailz/.claude/commands/pm/test-ghost.md'
Note: the file never existed — this is not a modification, it's a fresh create.
- After the error, the file is a ghost:
```
$ ls .claude/commands/pm/
decompose.md delegate.md learn.md logs.md qa.md quick.md
spec-feature.md status.md test-ghost.md # ← shows in listing
$ ls -la .claude/commands/pm/test-ghost.md
ls: cannot access '.claude/commands/pm/test-ghost.md': No such file or directory
``stat
The file appears in directory listings but , cat, rm -f, echo >, and git checkout all fail with ENOENT. Permissions show as ?????????`.
- Recovery requires bypassing WSL entirely:
```
cmd.exe /C "del /F D:\\Grailz\\.claude\\commands\\pm\\test-ghost.md"
cmd.exe
Even after deletion, the ghost may persist in the 9P cache until rm -f` is run from WSL or WSL is restarted.
Control test (same session, same project):
Writing to the project root (/mnt/d/Grailz/test.md) using the same Write tool succeeds without issue. Read, Edit, and re-Read all work. The bug appears specific to deeper/dot-prefixed directory paths (.claude/commands/pm/).
Root cause analysis
The Write tool calls statx() before writing (likely for the "file has been unexpectedly modified" safety check). On WSL2's 9P/drvfs layer, statx() can return ENOENT for files that genuinely exist — this is a documented 9P kernel driver bug (microsoft/WSL#13105). When this happens:
- The
statx()call poisons the 9P path cache for that file - All subsequent Linux syscalls for that path fail with ENOENT
- But the file still exists on NTFS — Windows-side tools can see and delete it
- The ghost persists until the file is deleted via
cmd.exeor WSL is restarted
Earlier CLI versions likely used a simpler stat call or skipped the pre-write check, never triggering the 9P bug.
Expected behavior
The Write/Edit tools should successfully write the file, or fail gracefully without corrupting the filesystem cache.
Suggested mitigations
- Detect drvfs mounts and use
statinstead ofstatxon those paths - Fall back gracefully — if
statxreturns ENOENT but the file was just read successfully, retry withstator skip the validation - Use direct writes instead of temp-file-then-rename on drvfs paths (NTFS doesn't support atomic rename-over-existing the way ext4 does)
Environment
- Claude Code version: 2.1.50
- OS: WSL2 Ubuntu on Windows 11 (kernel 5.15.153.1-microsoft-standard-WSL2)
- Filesystem: drvfs mount at /mnt/d/ (NTFS)
- Node.js: (via bun)
- Model: Claude Opus 4.6
- Single session, single file write, no parallel agents
Related issues
- microsoft/WSL#13105 — open(), rename(), fstat() → ENOENT on 9P
- microsoft/WSL#1662 — Filesystem accumulating corrupted entries
- #12805 — Edit/Write tools fail with "unexpectedly modified" on Windows
- #7918 — File Edit fails on Windows with unexpected modification error
- #15832 — EINVAL error watching temp file during atomic write
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗