Fix session scrollback loss on Sparkle updates and red-X window close

Closed
KO
1mo ago
+13-17

Summary

Two independent bugs caused the session snapshot (including scrollback) to be lost in ways that matched real-world reports of "cmux forgets my terminal content after restart":

  • Sparkle update relaunch race. updaterWillRelaunchApplication wrapped its cleanup in Task { @MainActor in … }, which schedules the body asynchronously and returns to Sparkle immediately. Sparkle then proceeded to terminate + relaunch while the snapshot save raced with applicationWillTerminate's own save + TerminalController.stop() sequence. Depending on the interleaving, the update could either skip the scrollback save entirely or overwrite a good snapshot with one captured after terminals had been torn down. Fix: run the body synchronously with MainActor.assumeIsolated so the snapshot is flushed to disk before Sparkle touches anything else. Pattern matches existing usage in GhosttyTerminalView.swift.
  • Red-X window close dropped scrollback and deleted the snapshot. unregisterMainWindow saved with includeScrollback: false and removeWhenEmpty: true for every red-X close while the app stayed running. That meant (a) remaining windows' scrollback was cleared on every close, and (b) closing the last window deleted the snapshot file entirely — so the next launch started blank instead of restoring the prior session. Fix: capture scrollback for the remaining windows and keep the snapshot file around even when no windows remain. The shouldPersistSnapshotOnWindowUnregister gate still skips this save during app termination, where applicationWillTerminate has already written a full snapshot with scrollback. The now-unused shouldRemoveSnapshotWhenNoWindowsRemainOnWindowUnregister policy function and its test assertions are removed.

Scope is intentionally limited to these two fixes. Broader capture-path improvements (scrollback on autosave, sleep/wake observers, etc.) are out of scope for this PR.

Test plan

  • Unit: `cmux-unit` scheme — existing testWindowUnregisterSnapshotPersistencePolicy still passes with the trimmed assertions.
  • Manual: open multiple windows with terminal content, close one with the red X, verify the remaining window's scrollback survives a Cmd+Q / relaunch cycle.
  • Manual: open cmux with terminal content, close the last window with the red X, relaunch the app, verify workspaces + scrollback are restored (previously: empty start).
  • Manual: trigger a Sparkle update install-and-relaunch, verify scrollback survives the update.
  • Manual: confirm ~/Library/Application Support/cmux/session-*.json contains scrollback fields after each of the above flows.

Notes for reviewers

I could not run a full local build — the machine doesn't have zig installed and GhosttyKit.xcframework is not prebuilt. The Swift diff is small (~20 lines across 3 files) and has been reviewed via the code-reviewer agent. Please run CI and a local build before merging.


Summary by cubic

Fixes two cases where session scrollback was lost: during Sparkle update relaunch and when closing windows with the red X. Scrollback now survives updates and relaunches, even after closing the last window.

  • Bug Fixes

    • Made updaterWillRelaunchApplication run synchronously via MainActor.assumeIsolated so the snapshot is flushed before Sparkle terminates, avoiding races with applicationWillTerminate/TerminalController.stop().
    • On unregisterMainWindow, save with includeScrollback: true and removeWhenEmpty: false while the app stays running, preserving scrollback and keeping the snapshot after last-window close; still skipped during app termination.
  • Refactors

    • Removed shouldRemoveSnapshotWhenNoWindowsRemainOnWindowUnregister and its test assertions.

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Improved session persistence reliability when closing windows to ensure data is consistently saved.
    • Enhanced the application update relaunch process to guarantee sessions and restorable state are properly persisted before restart, preventing potential data loss during updates.
  • Refactor

    • Streamlined internal session management code.