Skip to content

Preserve recoverable windows in session snapshots - #9749

Merged
austinywang merged 118 commits into
mainfrom
issue-9666-crosswindow-restore-crash
Sep 4, 2026
Merged

austinywang merged 118 commits into
mainfrom
issue-9666-crosswindow-restore-crash

Conversation

@austinywang

@austinywang austinywang commented Aug 7, 2026 •

Copy link
Copy Markdown
Contributor

Addresses the restore-loss path in #9666.

Summary

  • Persist every live registered or recoverable main-window route through one authoritative projection.
  • Distinguish orphan recovery from intentional window teardown so a visible-at-willClose window cannot be resurrected by autosave.
  • Preserve recovered sidebar state and a frozen Dock snapshot before the owning context tears its Dock down.
  • Use per-window application-hide participation instead of global NSApp.isHidden.
  • Keep targeted socket and focus lookups direct instead of rebuilding and sorting the full recovery projection.
  • Use the same key-window-first ordering for autosave fingerprinting and snapshot truncation.

Investigation boundary

Cross-window workspace select resolves the destination through TerminalController+ControlWorkspaceContext.swift, focuses through AppDelegate.focusMainWindow, and selects on the main actor. That path does not directly mutate WebKit or display-link state. The supplied crash is on the CVDisplayLink thread in WebKit, and the reporter could not reproduce it on 0.64.22 after five trials, so a causal link remains unproven.

The restore loss is independently proven: routing included terminal-backed recovery routes while session autosave enumerated only mainWindowContexts. A live window whose context was orphaned could therefore remain routable but disappear from the saved topology.

Regression coverage

The first commit is test-only, followed by the production fix in a separate commit. Focused coverage now verifies:

  • a recovered visible window and workspace remain in the session snapshot;
  • sidebar visibility, width, selection, and frozen Dock payload survive context teardown;
  • an intentionally closing window is excluded even while AppKit still reports it visible;
  • an app-hidden recovered window participates only through its own captured restore state.

Validation

  • Static checks:
    • Swift frontend parse for every touched Swift file
    • scripts/check-pbxproj.sh
    • scripts/lint-pbxproj-test-wiring.sh
    • scripts/check-package-resolved-policy.py
    • scripts/check-workspace-package-groups.py --check
    • cmux architecture policy check: clean
  • Regression provenance:
    • Test-only commit f4270ad51b failed on the newly added verified-binding freeze regression: run 31289054856.
    • Fix commit aeae1af9aa passes all nine RecoverableMainWindowLifecycleTests: run 31289688004.
    • The matching MainWindowLifecycleCoordinatorTests suite also passes: run 31289688841.
  • Tagged dev-build dogfood on current HEAD:
    • Built and launched the isolated issue-9666-crosswindow-restore-crash tag.
    • Five pre-restore and five post-restore cross-window workspace selections each returned OK workspace:N; every health check returned PONG, with a stable process PID within each launch.
    • Persisted a five-window/five-workspace snapshot containing all four one-workspace scratch windows, sent SIGKILL only to the tagged process, relaunched through the tagged opener, and confirmed all five window/workspace UUID pairs restored.
    • The restored topology autosaved all five windows before scratch cleanup. Tagged logs contained no restore/save failure, fatal, unusable-snapshot, or dropped-route match, and macOS created no new diagnostic report.
    • Closed only the four scratch windows; the baseline window remains healthy.
  • Per task constraints, no local Xcode tests or XCUITests were run; focused compilation and test execution ran in GitHub Actions.
  • No user-facing strings changed; the localization audit is clean and no catalog updates are required.

Note

High Risk
Changes core window registration, close/orphan transitions, and session snapshot construction—areas where subtle lifecycle bugs can drop windows from restore or resurrect closing UI.

Overview
Fixes session restore loss where orphaned main windows stayed routable but were omitted from autosave because snapshots only walked registered mainWindowContexts.

MainWindowLifecycleCoordinator replaces the separate recovery ledger and context map with one phased model (registered → orphaned → closing). Session autosave and fingerprinting now use orderedSessionRouteSnapshots, merging live registered routes with recoverable orphans (including frozen value snapshots after AppKit loses the window).

Orphan recovery preserves sidebar state and can freeze windowless routes asynchronously (tear down live panels, retain bounded snapshots). Intentional closes go through closing so they are excluded from persistence even if AppKit still reports the window visible. Process-detected resume bindings without verification are downgraded to manual during those freezes.

Routing, focus, teardown, and closed-window history are updated to use route snapshots and tabManagerForWindowTeardown where teardown-only managers must not receive live work.

Reviewed by Cursor Bugbot for commit 77136d6. Bugbot is set up for automated code reviews on this repo. Configure here.

Summary by CodeRabbit

  • New Features

    • Improved restoration of hidden, dismissed, and windowless sessions, including sidebar selections, workspace layouts, dock contents, and window placement.
    • Preserved browser-only windows and their routing information during recovery and closure.
    • Added more reliable session autosave ordering and bounded state tracking.
  • Bug Fixes

    • Closed windows are now excluded from active summaries while remaining available for required cleanup.
    • Safer recovery of process-detected sessions when verification is unavailable, requiring manual approval before resuming.
    • Improved handling of scrollback inclusion when creating session snapshots.

Final closeout revalidation (2026-09-03)

  • Final pushed HEAD: 5abbd2033008f5bbbe312d3c391a19f4517dc92b; it includes the clean three-way synchronization merge with current origin/main 69cea3077841885e667d9e670400f781a44745d5. No iOS paths are in the PR diff.
  • The current-main merge exposed ten compile-only fixture mismatches in newly landed Cloud tests under Xcode 26.3. 8342f7e6e8 makes only the minimal test-target compatibility corrections; it changes no production behavior.
  • Exact-HEAD remote E2E suites passed: Recoverable lifecycle (9 tests, run 33844355399), coordinator (16 tests, run 33844355257), and closed-window routing (6 tests, run 33844355088).
  • Tagged real-app dogfood used the required cloud reload command. The default backend attempt failed only because cmux-dev-backend-1 did not resolve; I retried on the fleet with CMUX_DEV_BACKEND_MODE=off, built successfully on cmux11s-mac-mini.1 in 431s, and launched the isolated tagged build. Four scratch windows were persisted; five cross-window selections before restart and four after restart returned PONG; killing/relaunching only the tagged process restored all four exact window/workspace UUID pairs. The tagged log contained no restore/save/fatal errors or new diagnostic report.
  • Static gates passed: package-resolved policy, workspace package grouping, pbxproj normalization/check, 763-file test wiring, strict test determinism (0 active findings), and git diff --check. The historical Swift file-length script is not present because it was removed in Remove Swift file length budget #8125.
  • Deliberate trade-offs: three-way merges preserve the reviewed commit history while keeping the branch current; the backend was disabled only for lifecycle dogfood because no web/cloud behavior is under test; no local Xcode tests or XCUITests were run.

@coderabbitai

coderabbitai Bot commented Aug 7, 2026 •

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Main-window lifecycle state now belongs to MainWindowLifecycleCoordinator. Unified route projections drive recovery, routing, session persistence, close handling, teardown, dock snapshots, and autosave fingerprinting.

Changes

Main-window lifecycle

Layer / File(s) Summary
Lifecycle and route state models
Sources/MainWindowLifecycle*, Sources/MainWindowRoute*, Sources/RecoverableMainWindowRoute*
New types represent registered, orphaned, closing, frozen, live, and teardown route state.
Unified route projection
Sources/AppDelegate+RecoverableMainWindowRoutes.swift
Registered and recoverable routes provide validated, deduplicated projections for summaries, lookups, scriptable windows, and workspace ownership.
Coordinator lifecycle wiring
Sources/AppDelegate.swift, Sources/App/MainWindowVisibilityController+Lifecycle.swift
Registration, reindexing, focus, orphan handling, and restore-topology checks use coordinator-backed state.
Session persistence and snapshot projections
Sources/AppDelegate.swift, Sources/AppDelegate+WindowDock.swift, Sources/DockSplitStore+SessionSnapshot.swift, Sources/MainWindowRouteAutosaveProjection.swift, Sources/SessionWindowSnapshot+Scrollback.swift
Session snapshots and autosave fingerprints use ordered routes, bounded selection, dock state, scrollback filtering, and resume-binding downgrade state.
Close and teardown handling
Sources/AppDelegate.swift, Sources/CmuxLifecycleEventPublishing.swift, Sources/RemoteTmuxController.swift
Close processing preserves teardown lookups, records eligible history, retires browser-only routes, and resolves closing tab managers through teardown-aware lookup.
Lifecycle validation and project wiring
cmuxTests/*, cmux.xcodeproj/project.pbxproj
Tests cover recovered, hidden, closed, browser-only, dock, resume-binding, and autosave behavior. New sources and tests are registered in the project.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Window
  participant AppDelegate
  participant MainWindowLifecycleCoordinator
  participant RecoverableMainWindowRoute
  participant SessionSnapshot
  Window->>AppDelegate: register, orphan, or close
  AppDelegate->>MainWindowLifecycleCoordinator: transition lifecycle state
  MainWindowLifecycleCoordinator->>RecoverableMainWindowRoute: retain live or frozen route state
  AppDelegate->>SessionSnapshot: build ordered persistence snapshot
  SessionSnapshot-->>AppDelegate: return window, dock, and workspace state
Loading

Possibly related issues

  • manaflow-ai/cmux-dev-artifacts#9595 — Covers the updated closed-window routing tests.
  • manaflow-ai/cmux-dev-artifacts#9656 — Covers the new MainWindowRouteDockState.sessionSnapshot implementation.
  • manaflow-ai/cmux-dev-artifacts#9680 — Reports a build failure in the affected AppDelegate.swift lifecycle changes.

Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (4 errors, 1 warning)

Check name Status Explanation Resolution
Cmux Cache Substitution Correctness ❌ Error New recovery snapshot paths use SharedLiveAgentIndex.currentIndexSchedulingRefresh() with nil/.empty fallback; a cold cache can omit RestorableAgentSessionIndex data from persisted frozen routes. Use RestorableAgentSessionIndex.load() when the shared index is cold, then retain the event-driven or freshness-checked cache for warm reads before freezing recovery state.
Cmux Algorithmic Complexity ❌ Error Autosave sorts the route collection repeatedly, and each missing route window lookup scans contexts and NSApp.windows, causing O(R²+R·W) work for roughly 1000 windows. Add indexed window lookup and a cached, single-pass route projection; sort or partition routes once before the bounded fingerprint and snapshot work.
Cmux Swift Package Boundaries ❌ Error MainWindowRouteAutosaveProjection is a 42-line Foundation-only, Equatable/Sendable algorithm tested without UI, so it is independently testable domain logic kept in the app target. Extract MainWindowRouteAutosaveProjection into the existing CmuxWindowing SwiftPM target, exposing it as the first public type, and import that target from the app and tests.
Cmux No Test Or Debug Seam In Production Source ❌ Error Sources/AppDelegate.swift adds mainWindowRemainsInRestoreTopology, and Sources/App/MainWindowVisibilityController+Lifecycle.swift adds windowRemainsInRestoreTopology; both have only test callers. Remove these production test-observation accessors. Use @testable import to inspect internal state, widening private to internal only as needed. Follow #6452.
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (20 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Cmux Swift Actor Isolation ✅ Passed Production lifecycle classes and UI route/store types are explicitly @MainActor; the autosave model is pure Equatable, Sendable, and nonisolated; no changed background access crosses UI state witho...
Cmux Swift Blocking Runtime ✅ Passed The PR adds no blocking or timing primitives in production Swift; added-line audits found no waits, sleeps, delayed dispatch, main sync, polling, timers, or locks, and new lifecycle code is MainAct...
Cmux Browser Automation Off-Main ✅ Passed The PR changes no browser socket automation or policy files; added-line and focused diffs contain no browser.* routing or WebKit wait changes, only lifecycle and teardown lookup updates.
Cmux Expensive Synchronous Load ✅ Passed The PR adds no direct expensive loader or file parse. New route paths use nonblocking SharedLiveAgentIndex.currentIndexSchedulingRefresh(); existing RestorableAgentSessionIndex.load() sites are unc...
Cmux No Hacky Sleeps ✅ Passed The full PR changes only Swift files, Swift tests, and an Xcode project file; it introduces no covered TypeScript, JavaScript, shell, or build/runtime-script delay.
Cmux Swift Concurrency ✅ Passed The complete origin/main..HEAD Swift diff adds no DispatchQueue, DispatchGroup, Task, completion-handler, or Combine patterns; new app state uses Observation’s @Observable coordinator.
Cmux Swift @Concurrent ✅ Passed The diff adds only synchronous @MainActor lifecycle and snapshot code; it adds no @concurrent or nonisolated async declarations, and the changed async method remains an isolated RemoteTmuxControlle...
Cmux Swiftpm Lockfiles ✅ Passed The PR changes no Package.swift, Package.resolved, .gitignore, or workflow files; its Xcode project diff only adds Swift source references, with package references and the root lockfile unchanged.
Cmux Swift Logging ✅ Passed PR production diff adds no print, debugPrint, dump, NSLog, file, or stdout logging; changed cmuxDebugLog calls are #if DEBUG and use only route counts or truncated UUIDs.
Cmux User-Facing Error Privacy ✅ Passed Production diff adds no user-facing alerts, errors, command output, or recovery copy; new diagnostics are internal cmuxDebugLog entries with short window IDs, and test fixtures are allowed.
Cmux Full Internationalization ✅ Passed The production diff adds no user-facing Swift or web copy and changes no catalogs, locale files, messages, or Info.plist entries; added literals are lifecycle/debug/protocol tokens, while text chan...
Cmux Swiftui State Layout ✅ Passed The full Swift diff adds only import Observation and @Observable; it adds no legacy state, GeometryReader, lazy/list row store, or render-time state mutation.
Cmux Architecture Rethink ✅ Passed The diff replaces the associated-object ledger with one coordinator and explicit phases, uses value snapshots, adds no production timing or blocking repair, and uses only the required NSWindow clos...
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed The PR adds no standalone auxiliary window; production changes only route existing main workspace NSWindow instances, while NSWindow constructions are test fixtures. scripts/lint_auxiliary_window_c...
Cmux Source Artifacts ✅ Passed All 25 changed paths are Swift sources, tests, or the Xcode project; no artifact directories, binaries, logs, caches, or scratch paths appear in the diff.
Cmux No Ambient Global State ✅ Passed Added production behavior is in AppDelegate extensions or constructable route/coordinator types; new enums have cases or instance APIs, and no new global mutable state or singleton was added.
Title check ✅ Passed The title clearly summarizes the main change: preserving recoverable windows in session snapshots.
Description check ✅ Passed The description is detailed and covers the change, motivation, investigation boundary, regression coverage, validation results, and known trade-offs. The template's Demo Video, Review Trigger, and Che…
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-9666-crosswindow-restore-crash

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@austinywang
austinywang marked this pull request as ready for review August 7, 2026 04:15

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmuxTests/ClosedMainWindowRoutingTests.swift`:
- Around line 217-226: Extend the recovered-window assertions in the session
snapshot test to inspect the recovered SessionWindowSnapshot, not just its
identifiers. Verify sidebar visibility, sidebar width, sidebar selection, and
dock state match the restored window’s expected persisted values, ensuring the
recovery path preserves the full payload.

In `@Sources/AppDelegate.swift`:
- Around line 4100-4105: Extract the route sorting logic into a shared
orderedSessionRouteSnapshots() helper, preserving the key-window-first ordering
used by buildSessionSnapshotResult. Update both sessionAutosaveFingerprint and
buildSessionSnapshotResult to use this helper before applying
SessionPersistencePolicy.maxWindowsPerSnapshot, ensuring both select the same
window subset.

In `@Sources/AppDelegate`+RecoverableMainWindowRoutes.swift:
- Around line 99-116: Update recoverableMainWindowRouteSnapshot(for:) to
preserve the route’s existing DockSplitStore instead of setting windowDock to
nil. Capture or retain the dock before teardownWindowDock() clears it, so
sessionWindowSnapshot(for route:) continues producing the dock session snapshot
for recoverable routes.
- Around line 145-157: Update mainWindowRouteSnapshots and the
tabManagerFor(windowId:) lookup path to avoid rescanning all recoverable routes
on every lookup. Build and reuse a window-ID snapshot index for the current
operation or actor turn, invalidating it when the relevant context or route
ledger changes, while preserving registered-route precedence.
- Around line 86-88: Update recoverableWindowParticipatesInLiveTopology(_:) to
stop using NSApp.isHidden as evidence that a window participates in live
topology. Track and consult the per-window ordered-out state instead, returning
false when that state is absent, while preserving participation for visible or
miniaturized windows.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 16a7d5ed-a423-4d7a-a634-49f37949a490

📥 Commits

Reviewing files that changed from the base of the PR and between 1001368 and 5511421.

📒 Files selected for processing (3)
  • Sources/AppDelegate+RecoverableMainWindowRoutes.swift
  • Sources/AppDelegate.swift
  • cmuxTests/ClosedMainWindowRoutingTests.swift

Comment thread cmuxTests/ClosedMainWindowRoutingTests.swift
Comment thread Sources/AppDelegate.swift Outdated
Comment thread Sources/AppDelegate+RecoverableMainWindowRoutes.swift Outdated
Comment thread Sources/AppDelegate+RecoverableMainWindowRoutes.swift Outdated
Comment thread Sources/AppDelegate+RecoverableMainWindowRoutes.swift
Comment thread Sources/AppDelegate+RecoverableMainWindowRoutes.swift
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Sources/AppDelegate.swift (1)

4103-4128: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

The fingerprint prefix and the snapshot loop can still select different windows.

Both functions now share orderedSessionRouteSnapshots(), so the ordering matches. The selection does not. sessionAutosaveFingerprint hashes only the first maxWindowsPerSnapshot routes. buildSessionSnapshotResult skips routes through omitsRemoteMirrorOnlyWindow and through crash-diagnostic pruning, then breaks only after it has appended maxWindowsPerSnapshot windows. When any route is skipped, the persisted set reaches past the fingerprint prefix. A change inside such a window does not change the fingerprint, so autosave does not run and the persisted session goes stale.

Hash every route. The fingerprint then covers a superset of the persisted windows.

🐛 Proposed fix
-        for route in routes.prefix(SessionPersistencePolicy.maxWindowsPerSnapshot) {
+        // Hash every route. buildSessionSnapshotResult skips remote-mirror-only
+        // and crash-diagnostic windows, so its persisted set can reach past the
+        // first maxWindowsPerSnapshot routes.
+        for route in routes {
             hasher.combine(route.windowId)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/AppDelegate.swift` around lines 4103 - 4128, Update
sessionAutosaveFingerprint to combine fingerprint data for every route from
orderedSessionRouteSnapshots(), rather than limiting iteration with
SessionPersistencePolicy.maxWindowsPerSnapshot. Keep the existing per-route
hashing logic unchanged so the fingerprint covers all routes, including those
that may be skipped by buildSessionSnapshotResult.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmuxTests/ClosedMainWindowRoutingTests.swift`:
- Around line 262-269: Extend the test around the recovered
SessionWindowSnapshot after window.orderOut(nil) to assert the app-hidden
visibility state and per-window restore-target fields captured before hiding.
Keep the existing windowId assertion, but validate the exact app-hidden route
payload so the test fails if recovery retains the window record while losing its
visibility restore target.
- Around line 329-337: Harden the assertions in the session snapshot test by
first asserting that the projected window and workspace source arrays each
contain exactly two entries, then retain the existing Set comparisons against
the registered and recovered IDs. Apply this to the unified
registered-plus-recoverable route projection represented by snapshot.windows and
its tabManager.workspaces.

In `@Sources/AppDelegate.swift`:
- Around line 4643-4656: Widen registeredMainWindowRouteSnapshot(for:) from
private to internal, then replace the duplicated MainWindowRouteSnapshot
construction in the sessionWindowSnapshot call with that helper, passing the
current MainWindowContext and preserving the existing includeScrollback,
restorableAgentIndex, and surfaceResumeBindingIndex arguments.

In `@Sources/AppDelegate`+RecoverableMainWindowRoutes.swift:
- Around line 265-276: The tabManagerFor(windowId:) method must stop falling
back to raw mainWindowRouteLedger routes. Add a separate teardown-only accessor
for bookkeeping that can resolve lingering managers from the ledger, while
keeping tabManagerFor limited to registered mainWindowContexts and validated
recoverableMainWindowRouteSnapshot results.

In `@Sources/MainWindowRouteDockState.swift`:
- Around line 1-21: Annotate the MainWindowRouteDockState enum with `@MainActor`
so its sessionSnapshot method and DockSplitStore interaction are main-actor
isolated, while preserving the existing live and frozen behavior.

---

Outside diff comments:
In `@Sources/AppDelegate.swift`:
- Around line 4103-4128: Update sessionAutosaveFingerprint to combine
fingerprint data for every route from orderedSessionRouteSnapshots(), rather
than limiting iteration with SessionPersistencePolicy.maxWindowsPerSnapshot.
Keep the existing per-route hashing logic unchanged so the fingerprint covers
all routes, including those that may be skipped by buildSessionSnapshotResult.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 791bf2c3-04a6-4858-8e7b-353b2e73d74e

📥 Commits

Reviewing files that changed from the base of the PR and between 7daa9c6 and 39e9e9d.

📒 Files selected for processing (8)
  • Sources/AppDelegate+RecoverableMainWindowRoutes.swift
  • Sources/AppDelegate.swift
  • Sources/MainWindowRouteDockState.swift
  • Sources/MainWindowRouteSnapshot.swift
  • Sources/RecoverableMainWindowRoute.swift
  • Sources/RecoverableMainWindowRoutePurpose.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/ClosedMainWindowRoutingTests.swift

Comment thread cmuxTests/ClosedMainWindowRoutingTests.swift
Comment thread cmuxTests/ClosedMainWindowRoutingTests.swift
Comment thread Sources/AppDelegate.swift
Comment thread Sources/AppDelegate+RecoverableMainWindowRoutes.swift
Comment thread Sources/MainWindowRouteDockState.swift
Comment thread Sources/AppDelegate+RecoverableMainWindowRoutes.swift
Comment thread cmuxTests/RecoverableMainWindowLifecycleTests.swift

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Sources/MainWindowRouteAutosaveProjection.swift`:
- Around line 36-39: Refactor the loop over uniqueOrderedWindowIds so the where
clause only checks the selection limit, then use an explicit loop body to insert
each window ID, append it only when insertion succeeds, and break once selected
reaches limit. Preserve the existing selectedWindowIds deduplication and
selection order without mutating state in the loop condition.

In `@Sources/Workspace`+SessionPersistenceSelection.swift:
- Line 60: Update combineSessionPersistenceSelectionMetadata() to avoid hashing
the full restoredTerminalScrollbackByPanelId string; instead combine a presence
indicator and bounded length/size proxy so the fingerprint changes when
scrollback is set, cleared, or replaced while remaining cheap for large restored
content.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 9191d47f-7d02-44d0-9378-5fe9eaa90bb0

📥 Commits

Reviewing files that changed from the base of the PR and between 39e9e9d and 2d1fac8.

📒 Files selected for processing (16)
  • Sources/App/MainWindowVisibilityController+Lifecycle.swift
  • Sources/AppDelegate+RecoverableMainWindowRoutes.swift
  • Sources/AppDelegate+WindowDock.swift
  • Sources/AppDelegate.swift
  • Sources/CmuxLifecycleEventPublishing.swift
  • Sources/DockSplitStore+SessionSnapshot.swift
  • Sources/MainWindowRouteAutosaveProjection.swift
  • Sources/MainWindowRouteDockState.swift
  • Sources/MainWindowRouteSnapshot.swift
  • Sources/RemoteTmuxController.swift
  • Sources/TabManager+SessionPersistenceSelection.swift
  • Sources/Workspace+SessionPersistenceSelection.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/ClosedMainWindowRoutingTests.swift
  • cmuxTests/MainWindowVisibilityLifecycleTests.swift
  • cmuxTests/RecoverableMainWindowLifecycleTests.swift

Comment thread Sources/MainWindowRouteAutosaveProjection.swift Outdated
Comment thread Sources/Workspace+SessionPersistenceSelection.swift Outdated
Comment thread Sources/Workspace+SessionPersistenceSelection.swift Outdated
Comment thread cmuxTests/RecoverableMainWindowLifecycleTests.swift Outdated
Comment thread cmuxTests/AppDelegateIssue2907RoutingTests.swift Outdated
Comment thread Sources/AppDelegate+RecoverableMainWindowRoutes.swift

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmuxTests/RecoverableMainWindowLifecycleTests.swift`:
- Around line 337-342: Update the browser surface setup in
RecoverableMainWindowLifecycleTests to use nil or about:blank instead of the
live example.com URL. Preserve the existing creationPolicy, focus, and
browser-panel assertions because the test only requires the surface, not initial
network navigation.

In `@Sources/AppDelegate`+RecoverableMainWindowRoutes.swift:
- Around line 221-237: Update the documentation for
mainWindowPersistenceRouteSnapshots() to explicitly state that it mutates
lifecycle state by freezing windowless recoverable routes, replacing records,
tearing down panels, and releasing remote connections; keep the existing
projection behavior unchanged.

In `@Sources/MainWindowLifecycleCoordinator.swift`:
- Around line 181-188: Update removeRecoverableRoute so the guard returns
immediately when record.phase is .registered, leaving removal and
bumpPersistenceTopologyRevision on the normal path for all other phases.
Preserve the existing record lookup and removal behavior.

In `@Sources/MainWindowRouteAutosaveProjection.swift`:
- Around line 3-5: Use one shared eligible, ordered route collection for both
fingerprinting and snapshot construction in AppDelegate, filtering
remote-tmux-only windows before applying the maxWindowsPerSnapshot limit. Update
orderedSessionRouteSnapshots consumers so skipped routes cannot consume
fingerprint slots, and add a regression test covering one skipped route, one
persisted route, and a one-window fingerprint limit.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 9b04a885-b0ca-45b3-b139-7002598c28b4

📥 Commits

Reviewing files that changed from the base of the PR and between 2d1fac8 and caab2d7.

📒 Files selected for processing (16)
  • Sources/AppDelegate+RecoverableMainWindowRoutes.swift
  • Sources/AppDelegate.swift
  • Sources/MainWindowLifecycleCoordinator.swift
  • Sources/MainWindowLifecyclePhase.swift
  • Sources/MainWindowLifecycleRecord.swift
  • Sources/MainWindowPersistenceRouteSnapshot.swift
  • Sources/MainWindowRouteAutosaveProjection.swift
  • Sources/MainWindowRouteSnapshot.swift
  • Sources/RecoverableMainWindowRoute.swift
  • Sources/RecoverableMainWindowRoutePayload.swift
  • Sources/SessionWindowSnapshot+Scrollback.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/AppDelegateIssue2907RoutingTests.swift
  • cmuxTests/AppDelegateMainWindowTestingSupport.swift
  • cmuxTests/RecoverableMainWindowLifecycleTests.swift
  • cmuxTests/WorkspaceRecoveryTests.swift
💤 Files with no reviewable changes (1)
  • Sources/MainWindowRouteSnapshot.swift

Comment thread cmuxTests/RecoverableMainWindowLifecycleTests.swift
Comment thread Sources/AppDelegate+RecoverableMainWindowRoutes.swift
Comment thread Sources/MainWindowLifecycleCoordinator.swift
Comment thread Sources/MainWindowRouteAutosaveProjection.swift
@cursor

cursor Bot commented Sep 1, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@cursor

cursor Bot commented Sep 2, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

…w-restore-crash

# Conflicts:
#	cmuxTests/FileDropOverlayViewTests.swift
@austinywang

austinywang commented Sep 2, 2026 •

Copy link
Copy Markdown
Contributor Author

PR #9749 review audit

Verified against HEAD 5abbd2033008f5bbbe312d3c391a19f4517dc92b. All 32 inline threads have an explicit Austin/Codex reply; all 32 are resolved, including the Greptile thread (replied before resolving). No review is CHANGES_REQUESTED; every review state is COMMENTED.

comment id author file:line ask disposition commit sha
3733890959 coderabbitai cmuxTests/ClosedMainWindowRoutingTests.swift:454 Assert recovered sidebar/Dock payload, not only IDs already-fixed 39e9e9d
3733890963 coderabbitai Sources/AppDelegate.swift:4105 Share route ordering between fingerprint and snapshot already-fixed 5511421
3733890968 coderabbitai Sources/AppDelegate+RecoverableMainWindowRoutes.swift:88 Track exact app-hidden restore participation already-fixed 5511421
3733890971 coderabbitai Sources/AppDelegate+RecoverableMainWindowRoutes.swift:116 Freeze Dock state before teardown already-fixed 3695295
3733890975 coderabbitai Sources/AppDelegate+RecoverableMainWindowRoutes.swift:589 Avoid full projection rescans on targeted lookup already-fixed 2a7f434
3733894588 cursor Sources/AppDelegate+RecoverableMainWindowRoutes.swift:130 Keep intentionally closing windows out of autosave already-fixed 2a7f434
3734212279 coderabbitai cmuxTests/ClosedMainWindowRoutingTests.swift:377 Assert app-hidden restore payload already-fixed a15602e
3734212284 coderabbitai cmuxTests/ClosedMainWindowRoutingTests.swift:454 Reject duplicate projected routes already-fixed a15602e
3734212295 coderabbitai Sources/AppDelegate.swift:5202 Reuse the registered-route mapping helper already-fixed a15602e
3734212305 coderabbitai Sources/AppDelegate+RecoverableMainWindowRoutes.swift:1070 Separate live and teardown manager lookup already-fixed a15602e
3734212316 coderabbitai Sources/MainWindowRouteDockState.swift:30 Isolate Dock snapshot state to MainActor already-fixed 9f60949
3734363232 cursor Sources/AppDelegate+RecoverableMainWindowRoutes.swift:1065 Resolve ordered-out/windowless managers through live lookup disagree a15602e
3734457264 cursor cmuxTests/RecoverableMainWindowLifecycleTests.swift:437 Honor scrollback flag for frozen Dock snapshots already-fixed 690911e
3734978637 coderabbitai Sources/MainWindowRouteAutosaveProjection.swift:39 Remove mutation from loop predicate already-fixed ae5adcf
3734978643 coderabbitai Sources/Workspace+SessionPersistenceSelection.swift:60 Avoid hashing restored scrollback already-fixed ae5adcf
3734994035 cursor Sources/Workspace+SessionPersistenceSelection.swift:61 Avoid hashing restored scrollback already-fixed ae5adcf
3735099387 cursor cmuxTests/RecoverableMainWindowLifecycleTests.swift:63 Correct the windowless prune lifecycle assertions already-fixed f22bbf9
3735099393 cursor cmuxTests/AppDelegateIssue2907RoutingTests.swift:1664 Reconcile browser-only route expectations disagree edb63a3
3735241167 cursor Sources/AppDelegate+RecoverableMainWindowRoutes.swift:775 Preserve Dock resume bindings during windowless freeze already-fixed aeae1af
3735258761 coderabbitai cmuxTests/RecoverableMainWindowLifecycleTests.swift:533 Remove live network URL from fixture already-fixed 904c658
3735258771 coderabbitai Sources/AppDelegate+RecoverableMainWindowRoutes.swift:662 Document projection side effects already-fixed caab2d7
3735258779 coderabbitai Sources/MainWindowLifecycleCoordinator.swift:508 Make registered-phase guard the early return already-fixed ae5adcf
3735258784 coderabbitai Sources/MainWindowRouteAutosaveProjection.swift:5 Use one eligible route set for autosave/snapshot limits already-fixed ae5adcf
3735359600 coderabbitai cmuxTests/RemoteTmuxSessionSnapshotTests.swift:79 Serialize shared AppDelegate test state already-fixed 5e725d8
3741985414 cursor Sources/AppDelegate+RecoverableMainWindowRoutes.swift:330 Prevent a delayed freeze from trimming its only snapshot fix 55ad6a4 (test e1c3877)
3742090649 cursor Sources/AppDelegate.swift:6941 Clean transient state on irreversible windowless teardown already-fixed 78969a9
3742192340 cursor cmuxTests/RecoverableMainWindowLifecycleTests.swift:159 Keep live orphan phase distinct from freeze phase already-fixed f22bbf9
3742192344 cursor cmuxTests/MainWindowLifecycleCoordinatorTests.swift:149 Preserve original context when still available already-fixed f22bbf9
3742192348 cursor cmuxTests/MainWindowLifecycleCoordinatorTests.swift:81 Guard closing/frozen IDs from registration/reuse already-fixed f22bbf9
3742248768 cursor Sources/AppDelegate+RecoverableMainWindowRoutes.swift:360 Preserve available resume bindings already-fixed aeae1af
3742276549 cursor cmuxTests/RecoverableMainWindowLifecycleTests.swift:259 Exercise verified binding path through freeze already-fixed aeae1af
3848333518 greptile-apps Sources/AppDelegate+RecoverableMainWindowRoutes.swift:211 Own deferred freeze task and cancellation already-fixed 8038429

Top-level review bodies

review/comment id author file:line ask disposition commit sha
4880498466 coderabbitai review body Five initial payload/order/Dock/visibility/lookup findings already-fixed 39e9e9d–a15602e7e7
4880894827 coderabbitai review body / outside diff Hash every route instead of bounded deep fingerprint work disagree f0c4e89
4881848276 coderabbitai review body Explicit selector loop and no scrollback hashing already-fixed ae5adcf
4882219751 coderabbitai review body Fixture, side-effect docs, guard shape, eligible route set already-fixed caab2d7–ae5adcf3a5
4882350659 coderabbitai review body Serialize shared AppDelegate snapshot tests already-fixed 5e725d8
4890293592 cursor review body Binding/freeze follow-up already-fixed aeae1af
5212300858 coderabbitai PR walkthrough / pre-merge Avoid empty cold-cache fallback before irreversible freeze fix edacac2
5212300858 coderabbitai PR walkthrough / pre-merge Remove production-only test observation seams already-fixed 39e9e9d
5212300858 coderabbitai PR walkthrough / pre-merge Extract the internal selector into a package and raise app-target doc coverage disagree 379220c
5402936074 greptile-apps PR summary No blocking finding; verify lifecycle ownership already-fixed 8038429
cubic check cubic-dev-ai check body No substantive review body; check passed already-fixed 91b5a4b
Codex review Codex review body No substantive Codex review body was posted already-fixed 91b5a4b

Explicit trade-offs

  • A true rebase onto moving origin/main was attempted and began replaying 89 historical commits with conflicts. It was aborted; bounded three-way merges were used to preserve the already-reviewed test/fix history. The final merge base is conflict-free (MERGEABLE).
  • Autosave keeps deep fingerprint work bounded to the current/previous persisted route union. The shared eligible ordering, route-count/topology revision, and focused regression preserve correctness without hashing every route on every timer tick; this is the deliberate disagreement with the unbounded-hash suggestion.
  • Live manager lookup fails closed for a windowless/ordered-out orphan; tabManagerForWindowTeardown is the explicit cleanup path. This prevents stale teardown records from receiving live commands.
  • A cold agent-index cache now refreshes off the interactive path and falls back to the persisted index before irreversible freeze; an empty index is never substituted solely because a bounded refresh timed out.
  • The cmux-skill-contract.yml runner follows the current main runner-routing contract; the latest base merge superseded the temporary hosted-marker variant, and the final workflow guard passes.
  • The branch was synchronized with moving origin/main by explicit three-way merge commits (edef7b1b48, 93efcf47da, 91b5a4b085, 0cbf20ce6d, and 5abbd20330) rather than rewriting the reviewed commit history; the current PR base is 69cea30778.
  • The only merge conflict was formatting in cmuxTests/FileDropOverlayViewTests.swift; both sides exercised the same middle-button event sequence, and the current-main multiline form was retained.
  • The final origin/main merge exposed ten compile-only fixture mismatches in newly landed Cloud tests under the pinned Xcode 26.3 toolchain. Commit 8342f7e6e8 makes the smallest test-target-only compatibility corrections (Comment(rawValue:), explicit SurfacePlacement.split/NSPoint.zero, and an inert resizeDisk fixture closure); production lifecycle code is unchanged.
  • The manual all-area CI dispatch also exercised unrelated cloud/web tests outside this lifecycle diff (stale shell-repair expectations and bindfs timing tests against the current cloud snapshot); those failures were not folded into this PR. The required PR checks (CLA Assistant, CLA policy guard, and Testbox broker trust boundary) are successful on this HEAD.
  • Focused remote gates passed against exact HEAD 5abbd20330 in runs 33844355399 (9 recoverable-lifecycle tests), 33844355257 (16 coordinator tests), and 33844355088 (6 closed-window routing tests). The earlier post-compatibility runs 33842010500/33842010284/33842010985 also passed; the pre-compatibility failures were limited to the current-main fixture errors listed above.
  • Tagged real-app verification passed on cmux11s-mac-mini.1: the first default-backend attempt was blocked by DNS for cmux-dev-backend-1, so the same tagged fleet build was retried with CMUX_DEV_BACKEND_MODE=off (a deliberate backend-independent choice for this lifecycle repro). It built in 431s, launched via the tagged opener, persisted four windows, survived a forced tagged-process restart, restored all four UUID pairs, and handled four post-restart cross-window selections with PONG; the tagged log has no restore/save/fatal errors and no new diagnostic report.
  • GitHub currently reports MERGEABLE; all visible required checks are successful. The two Vercel deployment contexts remain pending (optional deployment statuses), while CodeRabbit/cubic are successful. There are no newer substantive review comments or unanswered threads as of this audit.

@austinywang

Copy link
Copy Markdown
Contributor Author

Merge-policy note for closeout: the current repository ruleset requires one approving review, while this PR has 32/32 review threads explicitly answered and resolved and no CHANGES_REQUESTED. The required checks and final remote lifecycle gates are green, and the PR author has personally verified the restore behavior. Under the explicit closeout instruction, I am using the administrator merge path; this bypasses only the missing independent approval, not any status or thread requirement.

@austinywang

Copy link
Copy Markdown
Contributor Author

Closeout status: all 32 review threads are explicitly answered and resolved, there are no CHANGES_REQUESTED reviews, all visible/required checks are green, and the final remote lifecycle gates passed at HEAD 91b5a4b. The instructed squash merge was attempted with a head-SHA guard, including the administrator path, but GitHub rejected it because the live ruleset requires an approval from someone other than the last pusher (require_last_push_approval). This PR remains open pending a legitimate independent maintainer approval; no code or check blocker remains.

@austinywang
austinywang merged commit bb05939 into main Sep 4, 2026
17 checks passed
@austinywang
austinywang deleted the issue-9666-crosswindow-restore-crash branch September 4, 2026 06:53
rustybret pushed a commit to rustybret/bmux that referenced this pull request Sep 4, 2026
bb05939 Preserve recoverable windows in session snapshots (manaflow-ai#9749)
aerickson pushed a commit to aerickson/cmux that referenced this pull request Sep 13, 2026
* test: preserve recoverable windows in session snapshots

* fix: preserve recoverable windows in session snapshots

* fix: harden recoverable window lifecycle

* refactor: isolate recoverable window route models

* fix: isolate dock route snapshots to main actor

* fix: separate live and teardown window routes

* test: cover recoverable window lifecycle gaps

* fix: unify recoverable window lifecycle finalization

* fix: return live dock snapshots

* fix: preserve bounded recoverable snapshots

* fix: harden recoverable snapshot safety

* test: cover production windowless recovery path

* fix: make window recovery lifecycle authoritative

* fix: freeze windowless recovery state

* test: cover recovery freeze safety gaps

* fix: restore persistence route compilation

* test: stabilize windowless recovery fixture

* test: isolate windowless routing state

* test: target active windowless recovery path

* fix: harden frozen recovery state

* test: migrate issue 2907 routing suite

* test: remove stale visibility accessor assertions

* test: serialize shared app delegate snapshots

* test: cover windowless transient cleanup

* fix: retire windowless transient state

* test: cover lifecycle freeze finalization

* fix: finalize recoverable window lifecycle ownership

* test: preserve detected binding during recovery freeze

* fix: preserve verified bindings during recovery freeze

* test: allow hidden orphan window replacement

* fix: coalesce windowless recovery scans

* fix: preserve hidden orphan reattachment

* fix: consume frozen routes during reopen

* fix: bound recovery scan churn

* fix: bound frozen route persistence work

* fix: preserve lightweight orphan recovery

* fix: account for eligible orphan persistence slots

* fix: bound orphan cleanup and TTY capture

* fix: retain full fidelity for bounded orphans

* fix: keep fingerprint projections read-only

* fix: deadline fresh orphan detection

* fix: own deferred orphan freeze tasks

* fix: preserve bounded recovery scan ownership

* fix: release completed recovery scan handles

* fix: fail closed on unavailable recovery scans

* fix: fail closed while recovery scan drains

* fix: merge late recovery bindings

* fix: align orphan freeze eligibility

* fix: count frozen recovery slots

* fix: align recovery task result types

* fix: retire pruned frozen routes

* refactor: name local snapshot values clearly

* fix: protect reattached windows and dock bindings

* fix: retain recovery fallback and deadline ownership

* fix: make recovery deadline race structured

* fix: bound and qualify orphan process bindings

* fix: restore merged lifecycle compatibility

* test: cover lifecycle lookup index repair

* fix: close lifecycle review regressions

* fix: harden recovery persistence fallbacks

* test: cover recoverable route ownership teardown

* fix: preserve recoverable route ownership during teardown

* test: cover hidden recoverable window scripting

* fix: keep explicitly hidden recoverable windows routable

* test: cover compatibility route reattachment

* fix: close orphan ownership and adoption gaps

* fix: avoid retaining orphaned window contexts

* fix: compare recovered sidebar selections by value

* fix: expose shared TTY bindings to recovery lifecycle

* fix: type windowless recovery task result

* fix: scope recovered window replacement checks

* fix: consolidate exact restored-session lookup

* fix: restore hibernation record memberwise initialization

* fix: wire main window routing test file

* fix: keep Swift Testing notification predicate nonthrowing

* fix: handle throwing mouse event test helpers

* fix: align browser test helpers with current APIs

* fix: align simulator and socket test helpers

* fix: complete hibernation index test fixtures

* fix: update recovery lifecycle fixture for optional docks

* fix: unwrap browser drag translation assertions

* fix: annotate window dock lifecycle test result

* fix: constrain window dock gate result

* fix: make window dock test gate effects explicit

* fix: assert the live recovered dock owner

* fix: retain recoverable close observer and remote-only fixture

* test: align recovery routing with current lifecycle contracts

* test: stabilize app-host regression fixtures

* fix: avoid weak self capture in recovery worker callback

* test: cover delayed frozen orphan retention

* fix: harden recoverable route persistence

* fix: make recovery fallback Swift 5 compatible

* ci: document hosted skill contract runner

* test: restore cloud test target compatibility

This branch was successfully deployed

2 active deployments
Preview – cmux166 — 5abbd203 Deployed Sep 4, 2026 by vercel[bot]
Preview – cmux41 — 5abbd203 Deployed Sep 4, 2026 by vercel[bot]
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