[BUG] Cowork Edit/Write tools silently clamp final file size to pre-edit size on FUSE-mounted workspace (deterministic repro)
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
In Claude Cowork mode (Claude Desktop), the Edit and Write tools silently clamp the final on-disk file size to the pre-edit file size, regardless of the size of the new content.
- When the edit would grow the file, tail bytes are silently dropped.
- When the edit would shrink the file, the trailing space is filled with null bytes (
\x00).
No error or warning is emitted; both tools report "updated successfully."
This is distinct from (but related to) the cache-coherency bug tracked in #42520 and the non-atomic-config-write bug in #28809 / #29250. This issue is deterministic and reproducible on a fresh session with a single tool call, ruling out concurrency or session-boundary races.
Severity: data-destroying. Silent corruption of source files that Claude reports as successful writes. Affected users only discover the corruption when downstream compile/test fails — or worse, when a file compiles but is missing functionality.
Environment
- Platform: Claude Desktop, Cowork mode (not Claude Code CLI), Windows 11
- Mount layer: virtiofs → bindfs (v1.14.7,
rw,nosuid,nodev,relatime,user_id=0,group_id=0,default_permissions,allow_other) - Reproduced on: both plain
fuse(virtiofs passthrough, sessionoutputs/directory) andfuse.bindfs(user-selected workspace folder). Not mount-specific. - Tools affected:
Edittool,Writetool - Tools NOT affected: bash
sed -i, bashcat > file << EOF, bashcp— proven clean in control tests
Impact in practice
One user working on a data pipeline project has repeatedly lost working code to this bug across multiple sessions:
- 6 scripts corrupted simultaneously at session start — each clamped to its own prior size. Included small files (72-line
_paths.py, 3191 bytes) and large (2000-lineenrich.py, 85000 bytes). Pattern is size-independent. - Mid-session
enrich.pytruncation at byte 83751 — after an Edit that grew a function by ~114 lines. Exactly matches the "size clamped to pre-edit size" signature. standardize.pywith 47 null bytes at EOF — textbook shrink-case signature.
A downstream _audit.py script now catches these via py_compile + null-byte detection, but by then the damage is done: edit chains were built on corrupt files, and recovery requires reconstructing dropped tail content from prior-session Reads.
What Should Happen?
Expected behavior
EditandWritetool calls should produce a file whose size matches the actual intended content length. TheEdittool already knows the full new content from itsnew_stringparameter; theWritetool knows thecontentlength. Neither should end up shorter than the content they were given.
- If for any reason the on-disk result does not match the intended content length, the tool should return an explicit error to the agent. Silent clamping — with the tool reporting "updated successfully" — is the worst possible failure mode because it poisons downstream Edit chains that build on the assumption the last write was correct.
Requested fixes
- Short-term: Make the
EditandWritetools perform atomic writes (write to temp file →fsync→rename). On Windows NTFS, same-volume rename is atomic and would prevent partial writes. This is the same pattern proposed for the.claude.jsoncorruption in #28809 / #29250.
- Short-term: Return an explicit error on any write where final file size does not match intended content length. Do not trust the syscall sequence to have completed correctly without verification.
- Longer-term: Audit all filesystem operations across Cowork's mount layer for similar patterns. The fact that
git initon the same mount produces a 54-byte all-whitespace config file suggests FUSE-layer issues beyond these two specific tools.
Error Messages/Logs
No error messages — that is the bug. The tool reports "updated successfully" in all cases.
Byte-level evidence below.
===== Test 2 (shrink case), tail hex dump of the corrupted file =====
00000020: 206d 6174 6368 6564 202f 2074 6f74 616c ' matched / total'
00000030: 2c20 656c 6170 7365 6429 0a0a 0a69 6620 ', elapsed)...if '
00000040: 5f5f 6e61 6d65 5f5f 203d 3d20 225f 5f6d '__name__ == "__m'
00000050: 6169 6e5f 5f22 3a0a 2020 2020 6d61 696e 'ain__":. main'
00000060: 2829 0a00 0000 0000 0000 0000 0000 0000 '()..............'
00000070: 0000 0000 0000 0000 0000 0000 0000 0000 '................'
00000080: 0000 0000 '....'
Note `main()\n` followed by 28 null bytes (0x00) to fill to the original size.
===== Test 1 (grow case), MD5 verification =====
md5sum of first 95063 bytes of expected (bash-computed) result:
e0c4d80243911fdb92ea635b6eb67b7d
md5sum of actual file after Edit tool call:
e0c4d80243911fdb92ea635b6eb67b7d
The first `original_size` bytes of the intended result match exactly. The last 109 bytes are dropped with no error.
Steps to Reproduce
Deterministic reproduction recipe
Setup
- Start a fresh Claude Cowork session on Windows.
- Select a workspace folder on the host (mounted as bindfs into the session).
- Place any existing text file of known size in the workspace. Tests below use a 95063-byte Python file (2162 lines), but the bug is size-independent.
- Record original size and md5sum via bash (
stat -c %s <file>andmd5sum <file>).
Test 1 — Edit that grows the file
Edit tool call:
file_path: <absolute/path/to/file>
old_string: "<first 60 bytes of file...>"
new_string: "<first 60 bytes of file...>\n# MARKER_ADDING_22_BYTES"
- Expected: file grows by 22 bytes (95063 → 95085).
- Actual: file remains at 95063 bytes exactly. The last 22 bytes of the correctly-modified content are silently dropped. File ends mid-statement.
Test 2 — Edit that shrinks the file
Edit tool call:
file_path: <absolute/path/to/file>
old_string: "<60-byte original string...>"
new_string: "<27-byte replacement>"
- Expected: file shrinks by 33 bytes (95063 → 95030).
- Actual: file remains at 95063 bytes. Correct content up to byte 95030, then null bytes (
\x00) padding to byte 95063.
Test 3 — Write that grows a small target file
Target: existing 46-byte file. Write call with 500+ bytes of new content.
- Expected: file replaced with 500+ bytes.
- Actual: file is exactly 46 bytes. Contains only the first 46 bytes of the new content. Everything else dropped.
Test 4 — Bash control (proves mount layer is not the sole cause)
sed -i 's/<old>/<new>/' <same-file>
Produces correct file size every time on the same mount. No truncation, no null padding. This isolates the bug to the Edit / Write tool implementation, not the FUSE / bindfs layer beneath.
All four tests repro in a single session with a single tool call each — no compaction, no session boundary, no concurrent host-side editing.
Claude Model
Opus
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
N/A — Claude Desktop, Cowork mode (not Claude Code CLI). This bug is in the Cowork desktop client, filed here because related Cowork bugs (#42520, #38993) are tracked in this repo.
Platform
Other
Operating System
Windows
Terminal/Shell
PowerShell
Additional Information
Note on template mismatch
This bug is in Claude Desktop Cowork mode, not Claude Code CLI. The Bug Report template has CLI-specific required fields (Claude Code Version, Terminal/Shell). I've filled them best-effort (N/A for version, Other for platform/terminal) since there does not appear to be a Cowork-specific issue form and related Cowork bugs (#42520 FUSE cache coherency, #38993 virtiofs truncation) are tracked in this repo.
Suggested root cause
The signature — "final file clamped to pre-edit size, tail dropped or zero-padded" — is consistent with a write pattern that:
- Opens the file with
O_WRONLY | O_TRUNC(size → 0). - Writes the new content.
- Calls
ftruncate(fd, pre_edit_size)with the wrong size argument — passing the pre-edit length instead of the post-write position.
Alternatively, the Edit/Write implementation may be using a fixed-size buffer sized to the pre-edit length and writing only that many bytes. Either way, the fix is to use the new content's actual length — and ideally adopt the standard safe pattern: write to a temp file, fsync, rename() into place (same as proposed in #28809 / #29250).
Cross-references
- #42520 — Cowork FUSE Mount Cache Coherency Failure on Host-Side File Writes (same FUSE layer, related class of bug)
- #28809 —
.claude.jsoncorrupted (Unexpected EOF) — non-atomic config writes - #29250 — Non-atomic writes to
.claude.jsoncause file corruption - #38993 — Cowork virtiofs FUSE serves truncated/stale files
- #23053 — VSCode extension freezes when generating large file content for Write/Edit
- #27896 — Silent failure when model output is truncated before tool call emission (different mechanism, similar class: silent failures with no error)
Current workaround
Avoid the Edit and Write tools for any size-changing operation on FUSE-mounted workspace paths. Use bash (sed -i, heredocs, cat >>) instead. Pair with a downstream audit script that py_compiles every script and detects null-byte padding before any pipeline run. This is a load-bearing workaround — users shouldn't need it.
Additional environment details
- Bindfs mount options:
rw,nosuid,nodev,relatime,user_id=0,group_id=0,default_permissions,allow_other - Bindfs version: 1.14.7
- Session's own
outputs/directory (plain virtiofs, not bindfs) also exhibits the bug — this is not a bindfs-specific issue - Bash writes on the exact same paths are always correct size — isolates the bug to the Edit/Write tool implementations
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗