Skip to content

Fixes and improvements from running 2.4.1 on 4 PCs with large saves — how would you like them? #21

Open

Description

@ChakraFusion

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_PruneEnforcesRetention and TestSingleFileGameChain (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 following dev-public on 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

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

  1. "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)
  2. 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)
  3. A save folder on a different drive letter on another PC (D: vs E:) 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) and TestSettings_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

Activity

  1. ChakraFusion commented on Oct 5, 2026

    @ChakraFusion
    Author

    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.

  2. Omar-VF commented on Oct 11, 2026

    @Omar-VF
    Collaborator

    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 of main. I've moved #22, #23, #24, #25 and #28 there and approved their CI runs.

  3. ChakraFusion commented on Oct 12, 2026

    @ChakraFusion
    Author

    Thank you for working through these. I will have a look at them tomorrow afternoon.

  4. ChakraFusion commented on Oct 13, 2026

    @ChakraFusion
    Author

    Thanks for the review and for setting up dev-public.

  5. ChakraFusion commented on Oct 14, 2026

    @ChakraFusion
    Author

    The standalone fixes against dev-public are up:

    Two more came out of CI failures:

    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) and TestIgnored_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.

  6. Omar-VF commented on Oct 15, 2026

    @Omar-VF
    Collaborator

    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 in multiroot.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. 0038 is taken by 0038_peer_folder_trust on dev, which will reach dev-public later. 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.

  7. ChakraFusion commented on Oct 15, 2026

    @ChakraFusion
    Author
  8. ChakraFusion commented on Oct 15, 2026

    @ChakraFusion
    Author

    Status above is updated. In short:

  9. ChakraFusion commented on Oct 15, 2026

    @ChakraFusion
    Author

    A correction and two new CI results, now in the status above:

    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.

  10. ChakraFusion commented on Oct 15, 2026

    @ChakraFusion
    Author

    Follow-up on the Journey test: I ran it 20 times on dev-public with 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.

  11. ChakraFusion commented on Oct 15, 2026

    @ChakraFusion
    Author

    Two updates, both in the status above:

  12. ChakraFusion commented on Oct 15, 2026

    @ChakraFusion
    Author

    #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.

  13. ChakraFusion commented on Oct 16, 2026

    @ChakraFusion
    Author

    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.

  14. ChakraFusion commented on Oct 16, 2026

    @ChakraFusion
    Author

    #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.

  15. ChakraFusion commented on Oct 17, 2026

    @ChakraFusion
    Author

    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.

  16. ChakraFusion commented on Oct 19, 2026

    @ChakraFusion
    Author

    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 (branchHolds read the oldest snapshot, so TestBranchFollow failed whenever B's first snapshot came before its pull). #26, #27 and #51 are rebased onto today's dev-public, and their full suites pass locally apart from that helper and #89.

  17. ChakraFusion commented on Oct 19, 2026

    @ChakraFusion
    Author

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions