[BUG] Write/Edit tools leave NULL-byte tail padding when shortening files (no O_TRUNC) on Windows + virtiofs

Resolved 💬 3 comments Opened Jun 2, 2026 by aiken884 Closed Jul 5, 2026

[BUG] Write/Edit tools leave NULL-byte tail padding when shortening files (no O_TRUNC) on Windows + virtiofs

Background

This is a split-out of one specific failure mode originally bundled under master ticket (54891). The master ticket aggregated 11 distinct silent failures; per the suggestion in that ticket's "Note on the single-issue preflight" section, this issue isolates the no-O_TRUNC tail NULL padding mode so it can be triaged independently.

Distinct from sibling issues:

  • Not a UTF-8 boundary problem (handled in split-02)
  • Not a Read cache problem (handled in split-03)
  • Not a CRLF problem (handled in split-04)
  • Not a concurrency problem (handled in split-05)

The root cause appears to be a missing O_TRUNC (or ftruncate) call when the write payload is shorter than the existing file. Any write tool that opens the existing file for write, seeks to 0, and writes the new payload without truncating will leave the old tail bytes in place — and on some filesystems / drivers (virtiofs / Plan9 mount into Cowork sandbox observed) the slack bytes show up as 0x00 rather than the prior content.

Reproducer

Environment: Windows 11 + Cowork desktop app + virtiofs mount, vault on host NTFS.

  1. Create a vault file with longer content (~10 KB UTF-8, CJK-heavy):

``bash
python3 -c "print('一' * 3000)" > /vault/target.md
``

  1. From Claude Code, call Write (or Edit) on the same path with a much shorter payload (e.g. 200 bytes ASCII).
  2. Verify on host:

``powershell
Get-Item C:\path\to\vault\target.md | Select-Object Length
# Expected: ~200 bytes. Actual: ~10 KB (size unchanged).
`
`bash
python3 -c "d=open('/vault/target.md','rb').read(); print(len(d), d.count(b'\x00'))"
# Tail is NULL-padded; the first 200 bytes equal the intended payload.
``

  1. Read of the file from Claude Code returns the intended 200-byte payload (cache hit), masking the disk-level corruption.

Expected behavior

When a write tool reports success:

  1. On-disk size equals len(payload) byte-for-byte.
  2. No NULL padding is appended past the payload.
  3. If O_TRUNC / ftruncate cannot be performed (e.g. lock contention), the tool returns an error rather than success.

A reasonable implementation: open with O_WRONLY | O_TRUNC | O_CREAT, write payload, fsync(fd), close(fd), re-stat, and only return success if stat.st_size == len(payload). Or: write to path.tmp.<uuid>, fsync, os.replace(tmp, path) (atomic on POSIX, MoveFileEx on Windows).

Actual behavior

Tool returns success. On-disk size unchanged. Tail bytes from the prior file content are replaced with 0x00. Subsequent in-session Read returns the intended (cached) content, hiding the failure. Only out-of-band byte inspection reveals the corruption.

Two days of vault writes produced eight files with tail-NULL pollution (NULL count 1–36 each) plus one 37 KB → 2.6 KB rebuild (一人公司.md) with ~34 KB of NULL slack retained.

Environment

  • OS: Windows 11 (build 26100.x), NTFS host
  • Mount: virtiofs / Plan9 into Cowork Linux sandbox
  • Claude Code: 2.1.123
  • Cowork desktop app (2026-04-29 ~ 04-30 build)
  • File class: vault .md files, frontmatter + Han characters, 4–60 KB

Related

  • Parent master ticket: (54891)
  • Sibling splits: #54891-split-02 (UTF-8 boundary), #54891-split-03 (Read cache), #54891-split-04 (CRLF), #54891-split-05 (concurrent .claude.json)
  • Adjacent: (54847) (tool dispatch silent stall — same trust-gap class)
  • Adjacent: (55877) (rclone FUSE cache stale → Edit/Write corruption)

Workaround (current)

"Iron Rule 7" — write payload via bash heredoc to /tmp, then cp to vault path. Followed by 9-item verify (NULL count == 0, UTF-8 strict decode, LF tail, size match, hex tail clean, frontmatter parse, heading anchor list, bold-label count, table pipe count). Doubles task latency for every vault write.

---

Cross-reference: split from closed master ticket #54891 (auto-closed as not_planned 2026-06-01 by github-actions[bot]; reproducible bugs persist). Also related to #64592 (Cowork VM service cluster). Submitted by dispatcher via REST API on behalf of the issue author (Claude Team subscription holder, IT MSP production use).

View original on GitHub ↗

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