Skip to content

Sync → Interrupted pulls no longer leave torn files#118

Merged
revett merged 2 commits into
mainfrom
revett/fix/88
Jul 21, 2026
Merged

Sync → Interrupted pulls no longer leave torn files#118
revett merged 2 commits into
mainfrom
revett/fix/88

Conversation

@revett

@revett revett commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Resolves #88

Problem

  • Pulled content was written directly to its destination, so a crash or device sleep mid write left a truncated file at the path
  • The next snapshot read those torn bytes as a legitimate local edit, preserved them as a conflict copy, and pushed the corruption to the bucket and on to every other device

Changes

  • Pulls are now staged to a hidden temp file beside the destination and renamed into place, so the destination only ever holds its old content or the complete new content
  • Adapters whose rename refuses to overwrite fall back to remove then rename, leaving only an instant where the path is absent, which the next sync replans as a pull rather than a push
  • The integration test vault now ignores all dot prefixed files, matching how Obsidian never indexes hidden files, so staged writes can never appear in a snapshot

Why

  • Write then rename is the pattern mature sync tools (Syncthing, Dropbox) use for exactly this reason; every crash window now self heals through the existing plan machinery instead of propagating garbage

Greptile Summary

This PR fixes the torn-file corruption described in #88 by staging every pulled write to a hidden dot-prefixed sibling (.geode-tmp) and renaming it into place, so the destination holds either its full previous content or the complete new content — never partial bytes. The integration-test walk helper is updated to skip all dot-prefixed entries, matching Obsidian's own hidden-file exclusion so staged files cannot surface in a snapshot.

  • writeThroughTemp writes to the temp sibling and renames atomically; a rename failure is caught and, if the destination still exists, delegated to replaceViaAside which moves the destination aside first so a second failure restores the original rather than deleting it.
  • hiddenSiblingPath uses a deterministic naming scheme so any leftover from a previous crash is reclaimed by the next write to the same path, not accumulated.
  • Five new unit tests cover: new-file write, overwrite, non-overwriting adapter (mobile), rename failure with correct restore, and interrupted writeBinary.

Confidence Score: 5/5

Safe to merge — the staging logic is correct for every tested failure mode and the previous review concern about destination loss on a non-destination-existence rename error is now addressed by the restore path in replaceViaAside.

All three files are narrowly scoped to the write-through-temp path. The logic correctly preserves the destination in every crash window: interrupted writeBinary (destination untouched), successful rename (destination fully replaced), and a rename that fails for a non-destination reason (destination restored via aside). The test suite exercises each of these branches and the assertions match the implementation.

No files require special attention.

Important Files Changed

Filename Overview
src/vault/obsidian.ts Adds writeThroughTemp, hiddenSiblingPath, and replaceViaAside to stage pulls via a hidden sibling file and rename into place; all failure paths restore the destination correctly.
src/vault/obsidian.test.ts Adds five writer tests covering the new staging flow: happy path, overwrite, non-overwriting adapter, rename failure with restore, and interrupted write; all assertions are correct.
src/vault/fs.ts Broadens the walk skip from the literal .obsidian name to any dot-prefixed entry, hiding staged temp files from the integration-test vault snapshot.

Reviews (2): Last reviewed commit: "Address comment" | Re-trigger Greptile

Comment thread src/vault/obsidian.ts
@revett
revett merged commit 1d18fb2 into main Jul 21, 2026
10 checks passed
@revett
revett deleted the revett/fix/88 branch July 21, 2026 21:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Interrupted pulls leave torn files that then propagate as edits

1 participant