Repository navigation
Fixes and improvements from running 2.4.1 on 4 PCs with large saves — how would you like them? #21
Description
Status (updated 2026-10-19)
Everything goes into dev-public. Each PR's description has the details; this is the map.
How these were found. Every fix here started from a real failure, mostly a red CI run, and was reproduced before it was fixed: each comes with a test that fails without it. #61–#65 came out of a long run of the e2e tests on Windows runners with the race detector, about 155,000 test runs over 11 rounds. For example, "daemon never became ready" appeared 10 times in about 28,000 runs before #61 and not once in about 52,000 after. With all fixes together, one round ran about 33,000 tests without a failure. Where a cause is inferred rather than reproduced, the PR says so.
Merged — thank you: #22, #23, #24, #25, #28, #49, #50, #52, #53, #54, #55, #56, #57, #58, #61, #62, #63, #64, #65, #68, #69, #70, #72, #73, #74, #78, #80, #84, #90, #98, #99 and #100.
Open, in the order I'd suggest:
| PR | Why |
|---|---|
| #51 | A false conflict when a device edits again before the other has recorded what it took. Rebased; now also sends agreed the reliable way (#80). |
| #107 | Test only: TestReverseDeletionPropagation deleted the file while a second pull of the first sync was still in flight, which wrote it back (1 failure in 428 runs). |
| #106 | A device joining with an old copy of the save (from a backup or a copied folder) is no longer asked which to keep, when the other device has that copy in its history: a state both have held is a merge base. |
| #26 | Sync speed: batched small files, an "unchanged" manifest shortcut, presence probed on its own. |
| #27 | Closest devices first, by measured link speed. Migration 0039. Needs #26. Rebased today after #73 (only the catalogs conflicted). |
CI: red runs (as of 2026-10-17), and why
| Run | Where | Failing test | Cause |
|---|---|---|---|
| #375, #379, #384 | dev-public |
TestReportedWAN_AlternatingEditsMustNotConflict |
#71's push loop; fixed by #78 (merged). |
| #385 | #77 | TestJourney_DeviceSpecificConfigNeverTravels (step 2) |
Found at last, from the logs #57 prints: B's "sync-complete" report to A timed out (it has a 2 s limit and isn't retried), so A's base stayed a round behind and B's next save read as "both sides modified". Same failure as #314, #328, #334, #361. Fixed by #80 (merged). |
| #381 | dev-public |
TestCrossingSyncs_AFirstPullIsNotTakenForADifferentSave |
The same reports timing out, in both directions, over a minute: the receiving daemon seems to hold them while it applies a sync. Addressed by #80 (merged). |
| #394 | #80 | TestBranchFollow_FetchesTheOtherDevicesSaveNotDeletesIt |
A's once-a-minute sync switched it back to the old branch before B could follow: #85. |
| #386 | #52 | TestDeletion_SurvivesALostReportWhenServed |
Not reproduced: 0 failures in 648 runs with the race detector, under load, on current dev-public. Probably the same timing, fixed by #80. |
Waiting on you
- Reviews, in the order above.
Waiting on me
- Nothing open.
TestHistory_PruneEnforcesRetentionandTestSingleFileGameChain(one failure each, last week) did not recur in 648 runs each. An always-on run of the tests behind past unexplained CI failures keeps followingdev-publicon a slow Windows machine.
Postponed, as you asked: save versions, design issue #29 and draft PRs #30, #31, #32, #34 and #36 (#33, #35, #37 and #38 are closed, replaced by #84, #73, #53 and #55). I'll rebuild that stack on dev-public once the fixes above are in, without what has been merged or split out by then.
Test build: https://github.com/ChakraFusion/OpenSave/releases/tag/v2.4.1-fixes.9 (unofficial pre-release on my fork).
Background: the original post (2026-10-04)
Hi! I've been running OpenSave across four Windows PCs (LAN plus VPN), with some large saves. The largest is Project Zomboid at ~240k files. Along the way I found several bugs, a few of which delete or lose save data, and fixed them in my fork:
https://github.com/ChakraFusion/OpenSave/tree/fixes-2.4.1
- The fork passes your full CI (Linux and Windows, race detector, e2e).
- It has been tested by hand on all four PCs, including emptied folders, interrupted restores, offline changes and real conflicts.
- A test build for all platforms (unofficial pre-release on my fork): https://github.com/ChakraFusion/OpenSave/releases/tag/v2.4.1-fixes.9
I've split it into focused PRs, as the README asks. Every branch passes CI on its own. The ones marked standalone apply to v2.4.1 directly. The rest are stacked: each builds on the one before, and I'll rebase them as earlier ones land. The larger features are linked to issues so we can discuss the approach first, but the code is ready, so you can read it alongside.
Bug fixes
- "Keep mine" can delete files on the device whose version was kept: a 300-file save becomes 100 files on v2.4.1. Adds a guard against syncs that delete most of a save. (standalone)
- Changes OpenSave writes itself are taken for the game saving: snapshot floods during a sync, and syncs echoing back. A save the game writes right after a pull now still counts as the game's. (standalone)
- A save folder on a different drive letter on another PC (
D:vsE:) is found, and a drive this PC doesn't have no longer raises a warning. (standalone)
Performance
4. Manifests and snapshots on large saves: rescanning 240k files 13–19 s → 1.3 s; a follow-up snapshot 80 s → 9 s. (standalone)
5. Batched small-file pulls, an "unchanged" manifest shortcut and gzip: 5,000 files 42 s → 12 s. Presence is probed every 10 s. (stacked on 1)
6. Network-aware sync: link speeds are measured, the closest devices are synced first, and VPN block fetches no longer fail on a fixed 30 s limit. (stacked on 5)
7. Smaller memory footprint (GOGC 50, 1 GiB soft limit). (standalone, optional)
Larger features (stacked, in this order)
8. Save versions: syncs decide by version vectors instead of guessing from files. This is the foundation for most of what follows. → design issue #29
9. Snapshot integrity: never incomplete; identified by content, so every PC agrees and keeps each snapshot once; snapshots fully contained in newer ones are removed.
10. Launcher files (Steam's remotecache.vdf) aren't saves; an unwritable folder is reported once and retried after 30 min; the snapshot after a pull is faster.
11. Device settings: per-PC graphics/config files are kept out of sync, from Ludusavi's config entries and content detection, with per-file control.
12. Play sessions: sync is held while a game is played anywhere; games are detected wherever installed, protected games included; an activity safety net covers unrecognised games.
13. "Track game" from the game database; one folder, one game, with an offer to link entries; folders holding only Steam's files are ignored.
14. Putting back an emptied save takes the other device's save and version; emptying isn't a version until confirmed.
15. Database: WAL, batched deletion-record clears (put-back went from ~1 hour in SQLite to seconds), and free space handed back.
16. Restore writes as many files at once as the drive does best (HDD, SATA or NVMe), with progress and time left.
Coordination
- Migrations: mine continue after
0037_game_root_mapped, numbered 0038–0043 in stack order. I'll renumber them if anything lands in between. - i18n: I'll follow Proposal: i18n / localization support (starting with Simplified Chinese) #18 / i18n: extract the Home child components (en + zh-CN) #20. New UI text can go through the i18n setup once the components I touch are extracted, or I can do that extraction myself.
- Flaky tests on your CI: two of your own tests failed intermittently for me on plain v2.4.1,
TestJourney_DeviceSpecificConfigNeverTravels(CI) andTestSettings_AutoDeleteOldSnapshots_SweepsWhenSwitchedOn(locally under load). Happy to look into them if useful. - Extra save locations:
TestMultiRootScenario_ALateReportDoesNotHideADivergence("already in sync") failed twice on Windows runners for me. Without a merge base, an extra location falls back to comparing modification times against the peer-wide last-sync time, which any late report moves. I think save versions for extra locations would be the real fix, and I'm happy to do that as a follow-up once versions land.
Does this order and size work for you? Is there anything you'd rather not have, or would like done differently?
Where everything is
- Bug fixes: sync: "Keep mine" no longer deletes files; hold syncs that would delete a large part of a save #22 (Keep mine), watcher: changes OpenSave makes itself are not new saves #23 (own writes), Find a save folder on another drive letter; a drive this PC lacks is no warning #24 (other drive letter)
- Performance: Faster manifests and snapshots for saves of many files #25 (large saves), sync: batch small files, skip unchanged manifests, probe presence on its own #26 (sync speed, on sync: "Keep mine" no longer deletes files; hold syncs that would delete a large part of a save #22), sync: closest devices first, by measured link speed #27 (network-aware, on sync: batch small files, skip unchanged manifests, probe presence on its own #26), app: smaller memory footprint (GOGC 50, 1 GiB soft limit) #28 (memory, optional)
- Save versions: design issue Proposal: decide syncs by save versions instead of guessing from files #29, draft PR sync: decide by save versions, not by guessing from files #30
- Stacked on sync: decide by save versions, not by guessing from files #30, in order: snapshots: never incomplete, named by content, kept once #31 snapshot integrity → Launcher files are no saves; unwritable folders said once; faster mirror snapshot #32 launcher files / unwritable folders → Keep each device's game settings out of sync #33 device settings → Hold a game being played back from syncing until the session ends #34 play sessions → "Track game" from the game database; one folder, one game #35 Track game → A save put back is the other device's; emptying is no version until meant #36 put-back → Database: WAL, batched clears, and giving free space back #37 database → Restore as many files at a time as the drive does best, with progress #38 parallel restore
Activity
ChakraFusion commentedon Oct 5, 2026
rldr: I accidentally pushed opensave to its limit with my Project Zomboid save folder that was auto detected and had 240k files in it. I am running Tailscale with multiple PCs at 2 locations, so the network aware sync is a really cool feature to massively speed up syncs in such multi PC setups.
The other speed improvements are also real. And the versioning approach makes everything safer.
Feel free to try the demo 2.4.1.fixes3 release from my repo to test things out. A new "add Games" button with the direct possibility to link manually added games to steam ids and the 'currently in game' savestates + cleanup after session completion are very good as well imo.
Omar-VF commentedon Oct 11, 2026
Thanks for this, and sorry for the week of silence.
How we're taking these in: contributions now go into a new branch,
dev-public, instead ofmain. I've moved #22, #23, #24, #25 and #28 there and approved their CI runs.dev-publiconce sync: "Keep mine" no longer deletes files; hold syncs that would delete a large part of a save #22 is in. sync: closest devices first, by measured link speed #27's migration will need a new number (0038 is taken on our side).remotecache.vdfcounting as a save change) can stand alone againstdev-public, separate PRs for them would be welcome.ChakraFusion commentedon Oct 12, 2026
Thank you for working through these. I will have a look at them tomorrow afternoon.
ChakraFusion commentedon Oct 13, 2026
Thanks for the review and for setting up
dev-public.watcher: changes OpenSave makes itself are not new saves #23: answered on the PR. The flake comes from how extra locations record which files both devices hold (a lost sync-complete report, or a refresh that runs late), not from this change. The fix is in a separate small PR: sync: deletions in extra locations survive a lost report or a late refresh #49.
Find a save folder on another drive letter; a drive this PC lacks is no warning #24: changed to ask first, as requested.
sync: batch small files, skip unchanged manifests, probe presence on its own #26 / sync: closest devices first, by measured link speed #27: rebased onto
dev-public(sync: "Keep mine" no longer deletes files; hold syncs that would delete a large part of a save #22 drops out, since it's merged) and retargeted. For sync: closest devices first, by measured link speed #27's migration: which number should I use?dev-publicstill ends at0037_game_root_mapped, so I can't see what took 0038. Tell me the next free one and I'll rename it.Standalone fixes from the stack: happy to. These can be cut against
dev-publicwithout save versions, roughly in this order:remotecache.vdftreated as a launcher file, not a save change;.opensave.tmpnever archived;I'll start with 1 and 2 unless you'd prefer a different order.
ChakraFusion commentedon Oct 14, 2026
The standalone fixes against
dev-publicare up:remotecache.vdf(1). Its e2e test fails ondev-public: a real save change never reached the other PC.Two more came out of CI failures:
TestSoak_ManyAlternatingRoundsNeverConflicton sync: deletions in extra locations survive a lost report or a late refresh #49.On this repo's CI, #51 and #52 each failed one Windows e2e job, in tests their changes don't reach:
TestFreshRestore_SnapshotModeReportsWhatItImported(a single CLI daemon not starting) andTestIgnored_ASaveFromTheCloudLeavesAnIgnoredFileAlone(a cloud offer, with no launcher files involved; it passes 8/8 locally on the branch). Both passed on my fork. A re-run should clear them.#53 and #55 replace the draft PRs #37 and #38, and #52 and #54 cover parts of #32 and #31. Shall I close #37 and #38?
5 to 7 (Track game, per-device settings, play sessions) each need a small table, so they wait for the migration number asked about for #27.
Omar-VF commentedon Oct 15, 2026
Thanks, this is great work, and the write-ups make review easy.
Merged into
dev-public: #23, #24, #50 and #58 (plus #22, #25, #28 earlier).Needs a rebase onto
dev-public: #49 now conflicts with #42 inmultiroot.go, and #52 and #54 conflict in the watcher with #23's merge.Re-running: the one failed Windows e2e job on #49, #52, #56 and #57. #51's latest run is already green.
Migration number: please use 0039 for #27.
0038is taken by0038_peer_folder_trustondev, which will reachdev-publiclater. Later PRs that need a table can take 0040, 0041 and so on, in the order you open them.Drafts: yes, please close #37 and #38 now that #53 and #55 replace them. Your order for the remaining standalone fixes (5–7) is fine.
store: number the peer links migration 0039
ChakraFusion commentedon Oct 15, 2026
Thank you, and for merging #61–#64 as well.
dev-public: sync: deletions in extra locations survive a lost report or a late refresh #49 (with fix(sync): prefer agreed base over misleading file mtimes #42), Steam's remotecache.vdf is the launcher's file, not a save change #52 and snapshots: never of a save a sync is writing, never incomplete #54 (with watcher: changes OpenSave makes itself are not new saves #23), and also Database: WAL, batched clears of deletion records, and giving free space back #53, Restore as many files at a time as the drive does best, with progress #55 and sync: a lost report no longer brings back a file deleted right after #65, which conflicted after today's merges. Each has a note on what the rebase resolved. All open PRs merge cleanly now.0039_peer_links.sql(last commit there). sync: batch small files, skip unchanged manifests, probe presence on its own #26 is rebased under it, with no code changes.The status at the top of this issue is up to date.
ChakraFusion commentedon Oct 15, 2026
Status above is updated. In short:
TestReportedWAN_AlternatingEditsMustNotConflict(#337, #347 on Restore as many files at a time as the drive does best, with progress #55, #357 on the changelog PR). That's the push loop in Two devices push to each other forever when a save returns to an earlier state (since #42) #71, not the PRs' code.ChakraFusion commentedon Oct 15, 2026
A correction and two new CI results, now in the status above:
TestJourney_DeviceSpecificConfigNeverTravelsin #334 down to a slow shard. That was wrong. Its step-2 failure ("chapter 2", want "chapter 3") recurs (#314, #328, #334, #361), and is most likely the false conflict sync: a peer that pulled says what it now holds, so the base is recorded at once #51 fixes. I'm checking that locally with and without sync: a peer that pulled says what it now holds, so the base is recorded at once #51.So #51 and #71 now account for the recurring failures; merging #51 first would take the soak (and likely the Journey) failures off every PR's CI.
ChakraFusion commentedon Oct 15, 2026
Follow-up on the Journey test: I ran it 20 times on
dev-publicwith and without #51, and it passed every time both ways, so I couldn't confirm the link. Treat Journey as unexplained for now; the soak failure on #361 stays #51 (its log shows the false conflict). Once #57 is in, the next Journey failure prints its logs.ChakraFusion commentedon Oct 15, 2026
Two updates, both in the status above:
dev-publicwithout save versions or the rest of the draft stack. It needs no migration, so 0040 stays free for the next PR that adds a table. The description lists what was left out and why.ChakraFusion commentedon Oct 15, 2026
#74 is the small game-database PR I mentioned: after every weekly manifest download, the next scan or game tracked parsed the 17.7 MB YAML first (
opensave scan: 32 s instead of 3 s); now the download builds the index. It also stops the CLI e2e tests from downloading the manifest from GitHub.A correction to my last comment: upstream that pause hits the next scan and the next game tracked, not "the first snapshot" (that is where it showed on my fork). #74's description has the details.
store: number the peer links migration 0039
ChakraFusion commentedon Oct 16, 2026
Status above updated. Thank you for today's merges: #49, #56, #57, #68, #69, #70, #72, #73 and #74. #71's fix is up as #78. The Journey failure finally printed its logs (#385, thanks to #57): the report that a sync finished timed out, and the next edit read as a conflict. Details and the next fix in the status.
ChakraFusion commentedon Oct 16, 2026
#80 is up: the fix for the reports that were lost on a busy device, the cause of the Journey failure (#385) and of #381. Status above updated.
store: number the peer links migration 0039
ChakraFusion commentedon Oct 17, 2026
Status above updated after today's merges. Thank you: #52, #53, #55, #78 and #80 are in. #54 and #84 are rebased (#84 on top of #54) as asked, and so are #51 and #27. Next from me: the #81/#85 fix with migration 0041.
store: number the peer links migration 0039
store: number the peer links migration 0039
ChakraFusion commentedon Oct 19, 2026
Status above updated. Thank you for #54, #84 and #90. #89's fix and the narrower #91 fix are up as #99 and #100, plus #98, a one-line test-helper fix (
branchHoldsread the oldest snapshot, soTestBranchFollowfailed whenever B's first snapshot came before its pull). #26, #27 and #51 are rebased onto today'sdev-public, and their full suites pass locally apart from that helper and #89.ChakraFusion commentedon Oct 19, 2026
Status above updated. Thank you for #100. Two new ones: #106 (a device joining with an old copy of the save is no longer asked which to keep, when the other device has held that copy) and #107 (a test-only race fix in
TestReverseDeletionPropagation). Both pass CI.For the postponed save versions: I ran e2e scenarios for the cases sync still has to infer without them (three devices, clocks off by half an hour, two devices with no agreed state yet), about 220 times each. No newer save was lost without someone being asked. The old-copy case asked needlessly, which #106 fixes without versions. The one needless question left is a three-device chain (A→B→C, then A meets C), which only versions would avoid. So versions now look like a nicety rather than a safety fix, and can wait.
store: number the peer links migration 0039