Skip to content

fix: restore the newest loadable backup instead of the oldest - #1654

Open
SameDesu123 wants to merge 1 commit into
kwaroran:mainfrom
SameDesu123:fix/backup-restore-newest
Open

SameDesu123 wants to merge 1 commit into
kwaroran:mainfrom
SameDesu123:fix/backup-restore-newest

Conversation

@SameDesu123

@SameDesu123 SameDesu123 commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

PR Checklist

  • Required Checks
    • Have you added type definitions?
    • Have you tested your changes?
    • Have you checked that it won't break any existing features?
  • If your PR uses models1, check the following:
    • Have you checked if it works normally in all models?
    • Have you checked if it works normally in all web, local, and node-hosted versions? If it doesn't, have you blocked it in those versions?
  • If your PR is highly AI generated2, check the following:
    • Have you understood what the code does?
    • Have you cleaned up any unnecessary or redundant code?
    • Is it not a huge change?
      • We currently do not accept highly AI generated PRs that are large changes.

Summary

When database/database.bin fails to decode on startup, the web/Node restore path loads the oldest readable backup instead of the newest one. Stop at the first backup that loads, as the Tauri path already does.

Related Issues

None

Changes

  • In src/ts/bootstrap.ts, the forage (web/Node) restore loop goes through getDbBackups(), which returns backups newest first. On each success it calls setDatabase(), sets backupLoaded = true, and keeps going, so every readable backup is loaded in turn and the last one, the oldest, ends up as the live database. Add a break after a successful load.
  • The account sync restore loop has the same pattern. Add the same break there. In practice it rarely runs because getDbBackups() returns [] with useSync on the web.
  • The Tauri loop already guards with if (!backupLoaded) and is unchanged.

History

  • a585ce8 added the restore loops with no stop after success. At that time, forage getDbBackups() did not sort its keys.
  • 07817c9 ("fix backup") added the if (!backupLoaded) guard to the Tauri loop only. It also added the newest-first sort to forage getDbBackups(). From then on, the unguarded forage loop deterministically ended on the oldest readable backup.
  • dee8182 moved the code into bootstrap.ts unchanged.

Impact

  • With this fix, a corrupted save on web/Node is restored from the newest readable backup instead of the oldest.
  • Without it, the issue is worse than an old restore. The oldest backup becomes the live database, and each later save writes it back to database.bin along with a new backup of the same old data. After about 20 saves, pruning has removed every backup that held the newer data, so the newer data cannot be recovered.
  • Behaviour is unchanged when database.bin decodes normally, and on Tauri.

Additional Notes

  • pnpm check: 0 errors
  • Out of scope, but related issues I noticed:
    • The decoder can accept a partially broken backup. For example, it only warns on a missing REMOTE block, so a damaged newest backup can still "load".
    • If database.bin is missing entirely, a new empty save is created without trying the backups.
    • getDbBackups() prunes to 20 even when it is called from the restore path.

Footnotes

  1. Modifies the behavior of prompting, requesting, or handling responses from AI models. ↩

  2. Over 80% of the code is AI generated. ↩

When the save file fails to decode on the web/local and account sync
paths, the restore loop kept going after a successful load, so every
readable backup was loaded in turn and the oldest one won. Stop at the
first (newest) backup that decodes, matching the Tauri path.
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.

1 participant