Skip to content

Preserve Pi sessions after workspace restore - #7628

Merged
austinywang merged 6 commits into
manaflow-ai:mainfrom
silouanwright:fix-pi-workspace-restore-collapse
Jul 14, 2026
Merged

austinywang merged 6 commits into
manaflow-ai:mainfrom
silouanwright:fix-pi-workspace-restore-collapse

Conversation

@silouanwright

@silouanwright silouanwright commented Jul 8, 2026 •

Copy link
Copy Markdown
Contributor

Closes #7752.

Follow-up to #5607 / #5534.

Summary

  • Adds a regression test for restored Pi workspaces whose hook-store records still have old workspace IDs while live process detection sees fresh workspace IDs.
  • Preserves the hook-store session identity for ambiguous Pi latest-file detections when the stable panel/surface ID maps back to a unique same-kind hook record.
  • Still carries live process IDs from detection so the restored panel remains recognized as live without replacing its session id.
  • Guards the panel-only fallback against stale hook stores that make a panel ID ambiguous, including reuse across old workspaces or multiple sessions for the same old workspace/panel.

Root cause

#5607 preserves exact per-panel Pi hook records when process detection can only infer the newest JSONL session in a shared cwd. That exact guard depends on the hook record and process snapshot sharing the same (workspaceId, panelId).

On session restore, cmux can recreate workspaces with fresh workspace UUIDs while preserving the terminal surface/panel ID. In that case, the exact (workspaceId, panelId) lookup misses the old hook record, and Pi process detection can again collapse same-cwd panels to the newest JSONL session file.

This patch keeps the exact-key path first, then uses a same-kind panel-ID fallback only when that panel/kind maps to one old workspace/session pair. If stale hook records make the panel-only fallback ambiguous, cmux leaves the inferred process-detected session alone instead of choosing an arbitrary hook snapshot.

Verification

  • git diff --check
  • xcodebuild build-for-testing with external DerivedData/SourcePackages/TMP and CMUX_SKIP_ZIG_BUILD=1.
  • Before the ambiguity hardening follow-up, the new workspace-rotation regression passed locally via targeted XCTest. After the follow-up, local filtered app-hosted XCTest runs started producing empty result bundles, so CI should run the two added regressions.

Demo Video

N/A — unit-test-only session restore indexing fix.

Review Trigger (Copy/Paste as PR comment)

@codex review
@coderabbitai review
@greptile-apps review
@cubic-dev-ai review

Checklist

  • I tested the change locally
  • I added or updated tests for behavior changes
  • I updated docs/changelog if needed
  • I requested bot reviews after my latest commit (copy/paste block above or equivalent)
  • All code review bot comments are resolved
  • All human review comments are resolved

Summary by CodeRabbit

  • Bug Fixes
    • Improved AI session restoration to correctly match and order per-panel hook records even when workspace IDs rotate.
    • Enhanced “latest session” detection to prefer workspace+panel matching, and fall back to stable panel-id matching only when unambiguous—avoiding incorrect panel-only restores.
  • Tests
    • Added a regression test for Pi hook sessions that rotates workspace IDs while keeping fixed panel IDs, validating detected snapshot keys and restored per-panel session ordering.
    • Kept/confirmed coverage ensuring ambiguous panel IDs use detected latest-file logic rather than an unsafe fallback.

Note

Medium Risk
Changes session-index merge logic for Pi and other agents using latest-file inference; wrong ambiguity handling could mis-attribute sessions, but scope is limited to restore indexing with new regression coverage.

Overview
Fixes Pi session restore when cmux recreates workspaces with new UUIDs but keeps the same panel/surface IDs, so ambiguous “newest JSONL in shared cwd” process detection no longer overwrites per-panel hook identities.

RestorableAgentSessionIndex.load now indexes hook records by (panelId, agent kind) and marks candidates ambiguous when one panel ID maps to multiple workspace/session pairs (stale stores, restarts, cross-workspace reuse). For inferredLatestSessionFile detections, it still prefers exact (workspaceId, panelId) hook matches, then falls back to a stable panel-ID hook entry only when unambiguous, merging in live process IDs without replacing the session id. If the panel-only map is ambiguous, it does not pick an arbitrary hook snapshot and leaves the inferred detected session.

Adds regression tests for workspace-ID rotation (three panels keep distinct Pi session ids) and for ambiguous panel-ID hook history (restored snapshot stays on the detected newest session).

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

When restored cmux workspaces receive fresh workspace IDs, Pi process detection can fall back to the newest session file in a shared cwd and collapse panels onto one session. Preserve same-kind hook-store identity by stable panel ID for inferred latest-file detections, and add a regression test for workspace ID rotation.
@vercel

vercel Bot commented Jul 8, 2026

Copy link
Copy Markdown

Someone is attempting to deploy a commit to the Manaflow Team on Vercel.

A member of the Team first needs to authorize it.

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@coderabbitai

coderabbitai Bot commented Jul 8, 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

RestorableAgentSessionIndex.load now tracks stable panelId+kind hook candidates and uses them to reconcile inferred-latest sessions when workspace-panel matching is unavailable or ambiguous. Pi tests cover workspace-id rotation and ambiguous panel-id cases.

Changes

Stable Panel-Based Hook Resolution

Layer / File(s) Summary
Candidate map setup
Sources/RestorableAgentSession.swift
Adds the stable panelId+kind key and candidate storage, then populates it while loading hook records with replacement and ambiguity tracking.
Inferred-latest reconciliation and Pi tests
Sources/RestorableAgentSession.swift, cmuxTests/RestorableAgentSessionIndexTests.swift
Updates .inferredLatestSessionFile resolution to prefer the workspace+panel candidate and fall back to the stable panelId+kind candidate when unambiguous, then adds Pi restoration coverage for rotated workspace ids and ambiguous panel ids.

Estimated code review effort: 4 (Complex) | ~40 minutes

Sequence Diagram(s)

sequenceDiagram
  participant RestorableAgentSessionIndex
  participant detectedSnapshots
  participant SessionStore

  RestorableAgentSessionIndex->>RestorableAgentSessionIndex: load hook candidates by panelId+kind
  RestorableAgentSessionIndex->>RestorableAgentSessionIndex: resolve workspace+panel or stable panelId+kind candidate
  RestorableAgentSessionIndex->>detectedSnapshots: merge live process evidence
  RestorableAgentSessionIndex->>SessionStore: restore per-panel session entry
Loading

Possibly related PRs

  • manaflow-ai/cmux#6803: Both PRs modify hook-entry replacement and reconciliation logic in RestorableAgentSessionIndex.load.
  • manaflow-ai/cmux#7352: Both PRs change how detected/restored hook-session records are reconciled into per-panel session entries.
🚥 Pre-merge checks | ✅ 25
✅ Passed checks (25 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 diff only adds local value-type candidate indexing in RestorableAgentSessionIndex; no new MainActor isolation, service protocols, or shared mutable Sendable refs.
Cmux Swift Blocking Runtime ✅ Passed Full PR diff adds only synchronous indexing/test scaffolding; no semaphores, sleeps, asyncAfter, main-sync, waits, or manual locks appear.
Cmux Browser Automation Off-Main ✅ Passed Only RestorableAgentSession.swift changed; no browser.* routing or ControlCommandExecutionPolicy files were touched, so the off-main browser-automation rule is not implicated.
Cmux Expensive Synchronous Load ✅ Passed Diff only clarifies a comment inside RestorableAgentSessionIndex.load; no new synchronous load was added to a main-actor or interactive path.
Cmux Cache Substitution Correctness ✅ Passed PASS: The panel-ID fallback is only used when unambiguous, the cold path keeps the detected snapshot, and the call site documents the restore/staleness rationale.
Cmux No Hacky Sleeps ✅ Passed Only a Swift file changed, and it adds no sleep, timer, polling, or fixed-delay logic covered by the no-hacky-sleeps rule.
Cmux Algorithmic Complexity ✅ Passed The PR adds only an extra O(1) per-record dictionary index and fixed-size candidate checks; no new nested full-collection scans or hot-path rescans appear.
Cmux Swift Concurrency ✅ Passed Diff only adjusts synchronous restore-index matching and XCTest coverage; no new DispatchQueue, Combine, callback, or fire-and-forget Task patterns were introduced.
Cmux Swift @Concurrent ✅ Passed PASS: The commit only changes comments in RestorableAgentSession.swift; no async/@Concurrent declarations or UI-isolated heavy async call sites were introduced.
Cmux Swift File And Package Boundaries ✅ Passed Focused bug fix in an already oversized session-index file (+61/-16) with matching tests; no new mixed responsibilities or boundary violation.
Cmux Swiftpm Lockfiles ✅ Passed Only Swift source changed in HEAD; no Package.resolved, .gitignore, Xcode project, or workflow dependency files were touched.
Cmux Swift Logging ✅ Passed The only diff in Swift is a comment update; no print/debugPrint/dump/NSLog/Logger changes or sensitive logging were added.
Cmux User-Facing Error Privacy ✅ Passed Only internal matching logic and developer comments changed; no user-facing errors or sensitive details were added.
Cmux Full Internationalization ✅ Passed PR changes only internal session-index logic and developer comments; no new user-facing text or locale assets were added.
Cmux Swiftui State Layout ✅ Passed The only changed file is backend session-index logic, not a SwiftUI view; no new ObservableObject/@Published/GeometryReader/lazy row store refs/render-time state writes appear.
Cmux Architecture Rethink ✅ Passed PASS: It’s a localized correctness fix with a clear unique-panel/kind invariant, and it adds no sleeps, observers, locks, or split owners.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PASS: The PR only changes RestorableAgentSession.swift session-index logic; it adds no NSWindow/NSPanel/WindowGroup code or cmuxAuxiliaryWindowIdentifiers changes.
Cmux Source Artifacts ✅ Passed Only a Swift source file changed, and the hunk is a code comment clarification—no logs, caches, build output, or other artifacts were added.
Cmux No Test Or Debug Seam In Production Source ✅ Passed Production diff only changes panel-fallback logic/comment in Sources/RestorableAgentSession.swift; no DEBUG/test-only members or wrappers were added.
Cmux No Ambient Global State ✅ Passed No new file-scope funcs, mutable globals, or singletons were added; the new panel-ID candidate state is private and nested inside RestorableAgentSessionIndex.
Title check ✅ Passed The title clearly summarizes the main change: preserving Pi sessions after workspace restore.
Description check ✅ Passed The description covers summary, rationale, verification, demo note, review trigger, and checklist items, matching the template well.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@greptile-apps

greptile-apps Bot commented Jul 8, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR preserves Pi sessions after workspace restore. The main changes are:

  • Adds a panel-id and agent-kind fallback for restored Pi process detections.
  • Marks panel-only hook-store matches ambiguous when stale records share a surface across workspaces or sessions.
  • Keeps live process evidence while preserving the hook-store session identity for safe matches.
  • Adds tests for workspace ID rotation and ambiguous panel-id hook records.

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.

Important Files Changed

Filename Overview
Sources/RestorableAgentSession.swift Adds guarded panel-id matching for restored Pi session reconciliation.
cmuxTests/RestorableAgentSessionIndexTests.swift Adds tests for Pi workspace rotation and stale panel-id ambiguity.

Reviews (6): Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." | Re-trigger Greptile

Comment thread Sources/RestorableAgentSession.swift Outdated
Comment on lines +1202 to +1204
let sameKindStablePanelCandidate = sameKindPanelCandidate ?? hookCandidatesByPanelIdAndKind[
PanelIDKindKey(panelId: key.panelId, kind: detected.snapshot.kind)
]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Panel-Only Fallback Can Collide

When the hook store contains more than one Pi record with the same stable panelId and kind across different old workspaces, this fallback drops the workspace from the lookup and lets shouldReplaceHookEntry choose a single winner. A restored process whose exact workspace key misses can then receive another workspace's hook snapshot while keeping its own live process IDs, so the UI can show or resume the wrong Pi session for that panel.

Rule Used: Flag correctness-critical detection/identity deriv... (source)

@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: 1

🤖 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/RestorableAgentSessionIndexTests.swift`:
- Around line 778-789: The test in RestorableAgentSessionIndexTests is too weak
because a single detected session can satisfy detectedSessionIds.count == 1 even
if only one panel was detected, allowing snapshot(workspaceId:panelId:) to fall
back by panelId without proving full reconciliation. Strengthen the test by
asserting that every restored workspace/panel pair in restoredWorkspaceIds and
panels is present in detectedSnapshots before checking restored session IDs,
using the existing RestorableAgentSessionIndex.load and
snapshot(workspaceId:panelId:) flow to validate all restored panels were
actually detected.
🪄 Autofix (Beta)

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

Run ID: c3fea1cf-8abb-481c-851f-9e1b5e4b7bf1

📥 Commits

Reviewing files that changed from the base of the PR and between 76df8ce and b15f2eb.

📒 Files selected for processing (2)
  • Sources/RestorableAgentSession.swift
  • cmuxTests/RestorableAgentSessionIndexTests.swift

Comment thread cmuxTests/RestorableAgentSessionIndexTests.swift
Only use the panel-id restore fallback when a panel/kind maps back to one old workspace key. Add a regression test that keeps latest-file detection when stale hook records reuse the same panel ID across old workspaces.
@silouanwright

Copy link
Copy Markdown
Contributor Author

Addressed the panel-only collision concern in f717de4. The panel-id fallback now tracks whether a (panelId, kind) maps to more than one old workspace key and only uses the fallback when it is unambiguous. I also added testPiDetectedLatestSessionDoesNotUsePanelOnlyFallbackWhenPanelIdIsAmbiguous to cover stale hook records reusing the same panel ID across old workspaces.

Harden the workspace-rotation regression so it first verifies process detection produced snapshots for every restored workspace/panel key before asserting latest-file collapse and restoration behavior.
Comment thread Sources/RestorableAgentSession.swift Outdated
hookCandidatesByPanelIdAndKind[panelIDKindKey] = PanelIDKindCandidate(
panelKey: shouldReplace ? key : existingPanelIDCandidate.panelKey,
entry: shouldReplace ? entry : existingPanelIDCandidate.entry,
isAmbiguous: existingPanelIDCandidate.isAmbiguous || existingPanelIDCandidate.panelKey != key

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Same-panel sessions collide

This ambiguity check only flips when another hook record has a different PanelKey. A stale hook store can still contain two Pi sessions for the same old workspace, panel, and kind after the panel was reused or relaunched. In that case isAmbiguous stays false, shouldReplaceHookEntry picks one record by recency, and a restored process whose new workspace id misses the exact key can inherit the wrong hook-store session while keeping its live process IDs. Please treat a second distinct hook entry for the same (panelId, kind) as ambiguous, not only a second old workspace key.

Rule Used: Flag correctness-critical detection/identity deriv... (source)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in ceb8389. The ambiguity check now also treats a second distinct hook session for the same (panelId, kind) as ambiguous, even when it has the same old workspace/panel key. I expanded testPiDetectedLatestSessionDoesNotUsePanelOnlyFallbackWhenPanelIdIsAmbiguous to cover both same-key stale sessions and different-old-workspace stale sessions.

Harden the panel-id restore fallback so a second distinct hook session for the same panel/kind is ambiguous even when it uses the same old workspace key. Expand the ambiguity regression to cover both same-key and different-workspace stale hook records.

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

Caution

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

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

1221-1246: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Comment undersells the ambiguity criteria — mention the same-workspace hardening too.

The comment says panel-only restore is unsafe only when the surface "maps back to one old workspace," but the actual check at Line 1193 also marks the candidate ambiguous when two hook records share the same old (workspaceId, panelId) but have different sessionIds (per the PR's own hardening commit). A maintainer reading only this comment could "simplify" the ambiguity check to just the panelKey comparison and silently reintroduce the arbitrary-snapshot-pick bug this PR just closed.

📝 Suggested comment clarification
-            // Panel-only restore is safe only when this surface/kind maps back to one old workspace.
-            // Stale hook stores can otherwise reuse a surface id and make the fallback ambiguous.
+            // Panel-only restore is safe only when this surface/kind maps back to exactly one
+            // (workspace, session) pair. Both a stale hook store reusing a surface id across old
+            // workspaces, and a single old workspace recording more than one distinct session for
+            // this surface (e.g. the agent restarted before that workspace was ever rotated), must
+            // make the fallback ambiguous — otherwise shouldReplaceHookEntry would pick one session
+            // arbitrarily.
🤖 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/RestorableAgentSession.swift` around lines 1221 - 1246, Clarify the
ambiguity guard around the panel-only restore path in RestorableAgentSession so
it also reflects the same-workspace hardening, not just the “one old workspace”
case. Update the nearby comment and any related logic around
sameKindStablePanelCandidate / matchingHookEntry to state that a candidate is
ambiguous when multiple hook records share the same old workspaceId and panelId
but differ by sessionId, so the restore path must not collapse this to a simple
panelKey check. Keep the explanation aligned with the existing restore flow
symbols like detected.sessionIDSource, sameKindPanelIDCandidate, and
sameKindStablePanelCandidate.
🤖 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.

Outside diff comments:
In `@Sources/RestorableAgentSession.swift`:
- Around line 1221-1246: Clarify the ambiguity guard around the panel-only
restore path in RestorableAgentSession so it also reflects the same-workspace
hardening, not just the “one old workspace” case. Update the nearby comment and
any related logic around sameKindStablePanelCandidate / matchingHookEntry to
state that a candidate is ambiguous when multiple hook records share the same
old workspaceId and panelId but differ by sessionId, so the restore path must
not collapse this to a simple panelKey check. Keep the explanation aligned with
the existing restore flow symbols like detected.sessionIDSource,
sameKindPanelIDCandidate, and sameKindStablePanelCandidate.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: a04b53c1-8b85-4b51-b82b-f7dc40f51709

📥 Commits

Reviewing files that changed from the base of the PR and between b73a3b2 and ceb8389.

📒 Files selected for processing (2)
  • Sources/RestorableAgentSession.swift
  • cmuxTests/RestorableAgentSessionIndexTests.swift

Update the restore-index comment so it documents both ambiguity cases: panel reuse across old workspaces and multiple stale sessions for the same old workspace/panel.
…ore-collapse

# Conflicts:
#	Sources/RestorableAgentSession.swift
@austinywang
austinywang merged commit b041531 into manaflow-ai:main Jul 14, 2026
7 of 9 checks passed
hhsw2015 pushed a commit to hhsw2015/cmux that referenced this pull request Jul 16, 2026
* Preserve Pi sessions after workspace restore

When restored cmux workspaces receive fresh workspace IDs, Pi process detection can fall back to the newest session file in a shared cwd and collapse panels onto one session. Preserve same-kind hook-store identity by stable panel ID for inferred latest-file detections, and add a regression test for workspace ID rotation.

* Guard Pi panel fallback against ambiguous hook records

Only use the panel-id restore fallback when a panel/kind maps back to one old workspace key. Add a regression test that keeps latest-file detection when stale hook records reuse the same panel ID across old workspaces.

* Assert all restored Pi panels are detected

Harden the workspace-rotation regression so it first verifies process detection produced snapshots for every restored workspace/panel key before asserting latest-file collapse and restoration behavior.

* Treat same-panel distinct Pi hooks as ambiguous

Harden the panel-id restore fallback so a second distinct hook session for the same panel/kind is ambiguous even when it uses the same old workspace key. Expand the ambiguity regression to cover both same-key and different-workspace stale hook records.

* Clarify Pi panel fallback ambiguity guard

Update the restore-index comment so it documents both ambiguity cases: panel reuse across old workspaces and multiple stale sessions for the same old workspace/panel.

---------

Co-authored-by: Silouan Wright <silouan@example.com>
(cherry picked from commit b041531)
@austinywang austinywang mentioned this pull request Jul 19, 2026
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.

Pi sessions can still collapse after workspace restore when workspace IDs rotate

2 participants