Skip to content

fix(persistence): warn once and retry failed saves - #425

Merged
chhoumann merged 3 commits into
masterfrom
fix/surface-write-failures
Oct 12, 2026
Merged

chhoumann merged 3 commits into
masterfrom
fix/surface-write-failures

Conversation

@chhoumann

@chhoumann chhoumann commented Oct 12, 2026 •

Copy link
Copy Markdown
Owner

Summary

Show "PodNotes could not save your changes." once per failed-write streak, and retry pending changes on the existing 10-second checkpoint schedule. Recovery is silent, and retries stop after recovery or unload. Successful sessions add no idle writes.

Detection and retention already existed in f7400ba (#388). All 73 existing persistence tests pass on master 1587b27, including swallowed-write detection and pending-document retention. The live master reproduction already rejects saveSettingsStrict when Obsidian swallows an adapter error. This PR adds the missing Notice and automatic idle retry. It keeps saveData plus readback, including Obsidian's modification-time bookkeeping, merge behavior, and quit/unload ordering.

Fixes #385.

Verification

Node v22.23.2, with commands run through fnm exec --using 22 --:

Command Result
npm run lint Pass
npm run format:check Pass
npm run typecheck Pass
npm run build Pass
npm run test 1,716 tests pass; Svelte checks have zero errors or warnings
npm run test:tooling 10 pass
npm run docs:build 18 pages, valid links and 112 legacy fragments
TMPDIR="$PWD/.tmp" npm run test:e2e 77 pass in 12 files
npm run size 285,132 raw / 90,712 gzip bytes; +270 / +72 versus master

Exactly two new regression tests: Notice streaks and scheduled retry. Both fail against unchanged master and pass here. The retry test also covers repeated failures, zero idle writes after recovery, and stopping on unload. Removing the unload guard makes it fail.

Real Obsidian 1.14.4 on Linux, isolated vault podnotes-fix-surface-write-failures. Feed http://podnotes.test:33289/feed.xml, PodNotes Fixture / Fixture Episode 1, 60-second WAV. The probe rejects writes only to .obsidian/plugins/podnotes/data.json, changes skip-forward length from 15 to 21 through Settings, then seeks and briefly plays/pauses beyond 30 seconds. Both the adapter and original file are restored in finally.

Fault-injection result Master Branch
Notices across repeated failures 0 1
Strict save while failing Rejects Rejects
Disk during failure Unchanged Unchanged
Adapter recovers, then 11.5 seconds idle Old setting and progress remain Setting 21 and progress 30.636788 saved
Later strict save Writes retained changes No additional write

npm run count:writes -- start, report, and stop confirm parity for 24 seconds playing plus 11 seconds idle: 4 writes on both builds after an idle start, 3 on both after a recent pause, and zero extra idle writes. Payload traces show only progress changed; checkpoint gaps are 10,002-10,004 ms. The initial 3-versus-4 comparison used different times since the previous save; the matched runs reproduce both counts on both builds.

Verification report, full SHA-256 snapshots, scripts and logs.

Live data.json SHA-256 evidence
  • Master before failure, during failure and after idle recovery: d0c2d1638285b63f42806300b536f607fab1f0fb2426412f2c1993ef9f3ec319.
  • Master after manual retry: d8d9be76953b05abae70a8bd6c7b9dab6785110fbf3a5d7226b5898857dc7f91.
  • Branch before and during failure: a967a5a7804c0eae6dd24dae4fa2087dd23d9cd284b9e108a0cebfba4a4241a0.
  • Branch after automatic recovery, unchanged by later strict save: cc04e0e0931da45618748a0d815ace987423cb5d68bee6ade11bf7e4260c6d15.
Master: silent failure Branch: one Notice
Master failure Branch failure

Silent recovery screenshot.

The first full E2E attempt hit Chromium's Linux socket path limit during restart because its scratch directory was too long. The full rerun uses the shorter worktree-local .tmp; no source change was needed. Independent GPT and Claude reviews found no critical issues; the Claude coverage finding was addressed in the existing retry test.

Release impact

No schema, settings shape, API or URI changes; no migration. Existing unreadable-file refusals remain in effect. Read failures before a retry and the existing external-merge settlement edge are recorded as follow-ups in the evidence report.

Docs at release

Document the save-failure Notice, one warning per failure streak, retries of pending changes on the 10-second checkpoint schedule while the file remains readable, and silent recovery when saving succeeds. Published docs are unchanged.

Note

Retry failed data-file saves at checkpoints and warn once per streak

  • DataFileWriter now retries ordinary failed saves at checkpoint intervals and stops retrying once a save succeeds. The new onWriteFailed callback fires once per failed-write streak, showing an Obsidian Notice.
  • Adds WriteRefusedError for writes blocked by refusal state; these are rethrown without entering the retry or notify path.
  • Guards scheduleCheckpoint so an unloaded writer does not create a checkpoint timer.
  • Adds tests in writer.test.ts and main.persistence.test.ts for streak notification, recovery, and unload behavior.

Macroscope summarized 2e8c23f.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

Deploying podnotes with  Cloudflare Pages  Cloudflare Pages

Latest commit: 2e8c23f
Status: ✅  Deploy successful!
Preview URL: https://616b6704.podnotes.pages.dev
Branch Preview URL: https://fix-surface-write-failures.podnotes.pages.dev

View logs

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.

Persistence: verify and surface adapter write failures hidden by Obsidian saveData

1 participant