You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
filesystem: write_file/edit_file destroy file creation time (birthtime) and file identity due to atomic-rename write strategy #4512
write_file and edit_file replace existing files via a temp-file + fs.rename pattern (writeFileContent and applyFileEdits in src/filesystem/lib.ts). There is a security rationale in the code comments: atomic rename prevents symlink race conditions (TOCTOU) between path validation and write. However, this strategy has an undocumented side effect: every write to an existing file creates a new inode, which
destroys the file's creation timestamp (birthtime on macOS/APFS and BSD, creation time on Windows/NTFS, crtime on ext4), and
breaks file identity: hard links are severed, and inode-based file watchers/editors tracking the file lose it.
Notably, the server itself treats the creation timestamp as first-class metadata: get_file_info returns it as a dedicated created: field. Reporting this field but silently destroying it on each of its own write operations is internally inconsistent.
To reproduce
Create a file, note birthtime (e.g. stat -f "%SB" file.md on macOS, or via the server's own get_file_info).
Call edit_file (or write_file) on it.
Read birthtime again → it now equals the time of the edit.
(move_file correctly preserves birthtime, since rename keeps the inode.)
Root cause
writeFileContent falls back to temp-file + rename for existing files; applyFileEdits uses the same pattern unconditionally. rename replaces the target with a different file, so all creation metadata and identity of the original are lost.
Suggested fix
Preserve the symlink-safety property while writing in place:
fs.open(filePath, fs.constants.O_RDWR | fs.constants.O_NOFOLLOW) — fails with ELOOP if the path is a symlink, giving the same protection the rename pattern provides (no writes through symlinks swapped in after validation).
fstat the handle and verify it is a regular file (defense in depth; also allows re-checking the resolved path if desired).
ftruncate + write through the handle → same inode, birthtime, hard links, and watchers all preserved.
On POSIX platforms (macOS, Linux, BSD) O_NOFOLLOW is well supported. On Windows, symlink creation requires elevated privileges by default and the threat model differs; the current rename path could be retained there, or FILE_FLAG_OPEN_REPARSE_POINT semantics used.
Trade-off: In-place writes lose crash-atomicity (a crash mid-write can leave a partially written file, which the rename pattern avoids). If that property is considered essential, an alternative would be an opt-in mode (env var or tool argument, e.g. preserveFileIdentity) selecting the in-place strategy — or at minimum, documenting the metadata-destroying behavior of the current implementation in the tool descriptions.
Activity
ayushbunny11 commentedon Jul 11, 2026
Going with your suggestion to open with
O_RDWR|O_NOFOLLOW,fstatto confirm it's a regular file before touching it, then truncate + write.Keeping the old rename path on win32, since
O_NOFOLLOWisn't reliable there and symlinks already need admin rights to create on Windows anyway.Only real downside is losing crash-atomicity on
POSIX; a crash mid-write now leaves a truncated file instead of an untouched one.fix(filesystem): write in place to preserve birthtime and file identi…
fix(filesystem): write in place to preserve birthtime and file identi…
fix(filesystem): write in place to preserve birthtime and file identi…
fix(filesystem): write in place to preserve birthtime and file identi…
test(filesystem): characterize every tool in-process and gate coverag…
test(filesystem): characterize every tool in-process and gate coverag…
test(filesystem): characterize every tool in-process and gate coverag…
docs(filesystem): read-only files are refused by the in-place write (#…
Merge pull request #5022 from modelcontextprotocol/v2/fix/4512-filesy…
cliffhall commentedon Oct 5, 2026
Fixed by #5022, merged into
v2/main.fix(filesystem): overwrite existing files in place (modelcontextproto…