[BUG] Write/Edit tools create ghost files on WSL2 drvfs mounts (statx + 9P cache poisoning)

Resolved 💬 2 comments Opened Feb 23, 2026 by AndersHeie Closed Mar 24, 2026

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):

  1. Use Claude Code's Write tool to create a new file in a nested .claude/ subdirectory — e.g., .claude/commands/pm/test-ghost.md
  2. 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.

  1. 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
``
The file appears in directory listings but
stat, cat, rm -f, echo >, and git checkout all fail with ENOENT. Permissions show as ?????????`.

  1. Recovery requires bypassing WSL entirely:

``
cmd.exe /C "del /F D:\\Grailz\\.claude\\commands\\pm\\test-ghost.md"
`
Even after
cmd.exe 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:

  1. The statx() call poisons the 9P path cache for that file
  2. All subsequent Linux syscalls for that path fail with ENOENT
  3. But the file still exists on NTFS — Windows-side tools can see and delete it
  4. The ghost persists until the file is deleted via cmd.exe or 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

  1. Detect drvfs mounts and use stat instead of statx on those paths
  2. Fall back gracefully — if statx returns ENOENT but the file was just read successfully, retry with stat or skip the validation
  3. 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

View original on GitHub ↗

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