[PR #13745] fix(core): use atomic writes in storage to prevent data corruption on crash #14795

Open
opened 2026-02-16 18:19:33 -05:00 by yindo · 0 comments
Owner

Original Pull Request: https://github.com/anomalyco/opencode/pull/13745

State: open
Merged: No


What does this PR do?

Bun.write(path, data) truncates the target file before writing. If the process crashes between truncate and write, the file ends up filled with null bytes — valid size, zero content. This corrupts session/message JSON files and can make entire sessions unloadable.

Fixes #7607, #9673, #10904.

Related PRs: #7734 and #11646 address the same root cause with a broader scope (CLI repair/restore, retry/backoff, fsync, quarantine). This PR is a minimal fix focused only on preventing the corruption.

See also #13032 which adds graceful degradation on corrupted reads — complementary to this fix.

The fix is the standard write-to-temp-then-rename pattern:

  • Write data to a dotfile sibling (.basename.random.tmp) in the same directory
  • fs.rename() atomically to the target (atomic on same filesystem, POSIX guarantee)
  • Clean up the temp file if rename fails
  • On startup, remove any orphaned .tmp files left by a previous crash

Dotfile prefix ensures Bun.Glob("**/*") in Storage.list() never picks up temp files (it skips dotfiles by default).

All writes in storage.ts are covered: Storage.write(), Storage.update(), migrations, and the migration index.

How did you verify your code works?

  • Confirmed the root cause by hex-dumping real corrupted files from a crashed session — all null bytes, matching the O_TRUNC + crash pattern
  • 108 existing session tests pass, 0 fail
  • Verified Bun.Glob ignores dotfiles with a manual test
**Original Pull Request:** https://github.com/anomalyco/opencode/pull/13745 **State:** open **Merged:** No --- ### What does this PR do? `Bun.write(path, data)` truncates the target file before writing. If the process crashes between truncate and write, the file ends up filled with null bytes — valid size, zero content. This corrupts session/message JSON files and can make entire sessions unloadable. Fixes #7607, #9673, #10904. Related PRs: #7734 and #11646 address the same root cause with a broader scope (CLI repair/restore, retry/backoff, fsync, quarantine). This PR is a minimal fix focused only on preventing the corruption. See also #13032 which adds graceful degradation on corrupted reads — complementary to this fix. The fix is the standard write-to-temp-then-rename pattern: - Write data to a dotfile sibling (`.basename.random.tmp`) in the same directory - `fs.rename()` atomically to the target (atomic on same filesystem, POSIX guarantee) - Clean up the temp file if rename fails - On startup, remove any orphaned `.tmp` files left by a previous crash Dotfile prefix ensures `Bun.Glob("**/*")` in `Storage.list()` never picks up temp files (it skips dotfiles by default). All writes in `storage.ts` are covered: `Storage.write()`, `Storage.update()`, migrations, and the migration index. ### How did you verify your code works? - Confirmed the root cause by hex-dumping real corrupted files from a crashed session — all null bytes, matching the `O_TRUNC` + crash pattern - 108 existing session tests pass, 0 fail - Verified `Bun.Glob` ignores dotfiles with a manual test
yindo added the pull-request label 2026-02-16 18:19:33 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: anomalyco/opencode#14795