Skip to content

Preserve restored agent resume bindings - #8885

Closed
azooz2003-bit wants to merge 5 commits into
mainfrom
fix-restored-agent-resume-bindings
Closed

azooz2003-bit wants to merge 5 commits into
mainfrom
fix-restored-agent-resume-bindings

Conversation

@azooz2003-bit

@azooz2003-bit azooz2003-bit commented Jul 24, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • preserve binding-only agent-hook resume bindings when prompt-idle reconciliation clears transient restored-agent state
  • keep stale/completed pruning in scan-backed persistence reconciliation
  • add regression coverage for prompt-idle preservation followed by liveness-scan pruning

Verification

  • ./scripts/lint-pbxproj-test-wiring.sh
  • xcodebuild test -project cmux.xcodeproj -scheme cmux-unit -configuration Debug -destination platform=macOS -derivedDataPath ~/Library/Developer/Xcode/DerivedData/cmux-rsbind -only-testing:cmuxTests/WorkspaceIsStaleAgentHookBindingTests blocked before executing tests by unrelated sidebar test compile errors already present in the test target: SidebarWorkspaceRowRetirementTests.swift, SidebarWorkspaceRowSuspensionTests.swift, SidebarWorkspaceTableSuspensionTests.swift. Result bundle: ~/Library/Developer/Xcode/DerivedData/cmux-rsbind/Logs/Test/Test-cmux-unit-2026.07.24_14-52-15--0700.xcresult
  • ./scripts/reload-cloud.sh --tag rsbind succeeded, run https://github.com/manaflow-ai/cmux/actions/runs/30130017800
  • tagged socket dogfood: launched cmux DEV rsbind.app, verified identify targeted /tmp/cmux-debug-rsbind.sock, set surface.resume.set via raw RPC with source=agent-hook and auto_resume=true, confirmed immediate binding, then confirmed fake checkpoint with no matching live process was pruned after the liveness scan

Notes

No user-facing strings changed; localization audit: source/test-only behavior change, no UI strings, menus, settings, docs, or command help modified.

Autoreview was not invoked.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


Summary by cubic

Preserve binding-only agent-hook resume bindings after prompt-idle so auto-resume survives restore, and allow trusted hook checkpoints to auto-resume even if the snapshot marked the agent as exited or unknown; stale bindings are still pruned by liveness. Adds regression coverage.

  • Bug Fixes
    • Stop clearing agent-hook resume bindings when a restored panel enters prompt-idle.
    • Auto-resume trusted agent-hook checkpoints (valid kind + checkpoint with autoResume=true) even when wasAgentRunning is false or unknown.
    • Keep liveness pruning for bindings with no matching live process on the same panel/checkpoint.

Written for commit 737e2f2. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Bug Fixes

    • Preserved durable agent-hook surface resume bindings when a restored agent transitions to prompt-idle.
    • Adjusted restored-agent auto-resume eligibility to use computed resume binding identity rather than legacy defaults.
    • Improved session snapshot/restore pruning to reflect the latest binding and resume decisions.
  • Tests

    • Added regression coverage for prompt-idle durable bindings and liveness cleanup.
    • Updated auto-resume tests for agent-hook bindings when the recorded agent running state is exited or unknown.
    • Enhanced snapshot assertions for startup scripts and restored resume state.

@coderabbitai

coderabbitai Bot commented Jul 24, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Auto-resume eligibility now considers restored agent state and trusted checkpoint bindings through a shared Workspace helper. Prompt-idle cleanup retains durable agent-hook bindings until liveness pruning, with expanded restore and lifecycle regression tests.

Changes

Agent resume lifecycle

Layer / File(s) Summary
Define auto-resume eligibility
Sources/SessionPersistence.swift, Sources/Workspace.swift
Adds checkpoint identity validation and centralizes auto-resume decisions for disabled, running, unknown, and trusted exited-agent states.
Integrate policy into restore and snapshot flows
Sources/Workspace.swift, Sources/DockSplitStore+SessionSnapshot.swift, Sources/DockSplitStore+SessionRestore.swift, cmuxTests/AgentSessionAutoResumeSettingsTests.swift
Session snapshots and terminal restoration use the shared eligibility helper; tests cover trusted bindings when the saved agent state is exited or unknown.
Preserve bindings during prompt-idle transition
Sources/Workspace+AgentLifecycle.swift, cmuxTests/WorkspaceIsStaleAgentHookBindingTests.swift, cmuxTests/AgentSessionAutoResumeSwiftTests.swift
The .promptIdle branch clears restored resume state while retaining surface bindings, which are later removed by liveness pruning tests.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SessionSnapshot
  participant Workspace
  participant BindingSnapshot
  participant TerminalRestore
  SessionSnapshot->>Workspace: evaluate restored agent state and binding
  Workspace->>BindingSnapshot: validate checkpoint identity
  BindingSnapshot-->>Workspace: return binding eligibility
  Workspace-->>SessionSnapshot: return auto-resume decision
  TerminalRestore->>Workspace: apply shared auto-resume policy
  Workspace-->>TerminalRestore: restore or skip resume command
Loading

Possibly related PRs

Suggested reviewers: austinywang, lawrencecchen


Important

Pre-merge checks failed

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

❌ Failed checks (1 error, 1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Cmux Swift Package Boundaries ❌ Error New auto-resume eligibility logic lives in app-target Sources/, but CmuxWorkspaces already owns restore-policy boundaries and tests this kind of pure domain logic. Move Workspace.shouldAutoResumeRestoredAgent into Packages/macOS/CmuxWorkspaces/Sources/CmuxWorkspaces/Session/WorkspaceSessionRestorePolicyService.swift, and hang the binding checkpoint check off the package binding protocol.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Cmux No Ambient Global State ❓ Inconclusive placeholder need evidence
✅ Passed checks (22 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 The patch keeps Workspace/DockSplitStore on @MainActor, adds only pure nonisolated helpers over Sendable snapshots, and introduces no new cross-actor UI access.
Cmux Swift Blocking Runtime ✅ Passed Touched Swift changes are pure policy logic and test assertions; no new blocking waits, sleeps, sync dispatch, polling, or locks were introduced.
Cmux Browser Automation Off-Main ✅ Passed Diff only touches session-restore/persistence and tests; no browser.* routing, socketWorkerMethods, or processV2Command main/worker changes were introduced.
Cmux Expensive Synchronous Load ✅ Passed Diff only refactors auto-resume checks into a pure nonisolated helper and local snapshot property; no new synchronous agent-history load was added.
Cmux Cache Substitution Correctness ✅ Passed The new helper still gates on scan-backed indexes and the snapshot path prunes stale agent-hook bindings via reconcileSurfaceResumeBindings/isStaleAgentHookBinding; no unhandled cache swap found.
Cmux No Hacky Sleeps ✅ Passed PASS: The PR only changes Swift source/tests; no TypeScript, JavaScript, shell, or build/runtime files introduced any delay-based coordination.
Cmux Algorithmic Complexity ✅ Passed PASS: The PR only adds O(1) auto-resume checks and a computed property; no new nested scans, sorts, or per-target rescans appear in the diff.
Cmux Swift Concurrency ✅ Passed Diff only adds a synchronous auto-resume helper and related policy calls; no new DispatchQueue, Combine, completion-handler, or fire-and-forget Task patterns were introduced.
Cmux Swift @Concurrent ✅ Passed PASS: the patch only adds a synchronous nonisolated helper/property and rewires callers; no async work, @concurrent misuse, or actor-hop regression appears in the diff.
Cmux Swiftpm Lockfiles ✅ Passed PASS: The PR only changes Swift source files; no Package.resolved, Package.swift, .gitignore, workflow, or Xcode project/package-reference files were modified.
Cmux Swift Logging ✅ Passed The diff only changes auto-resume logic and tests; it adds no print/debugPrint/dump/NSLog or new Logger usage in production Swift.
Cmux User-Facing Error Privacy ✅ Passed Patch only changes restore/snapshot logic and a computed property; no user-facing errors, alerts, command output, or recovery copy were added.
Cmux Full Internationalization ✅ Passed Diff only changes restore/session logic and tests; no user-facing Swift text, catalogs, Info.plist, or web i18n files were added or edited.
Cmux Swiftui State Layout ✅ Passed HEAD changes only add restore/snapshot helpers and a computed property; no new SwiftUI state/layout patterns, GeometryReader, lazy-row stores, or render-time state writes.
Cmux Architecture Rethink ✅ Passed Diff centralizes auto-resume in one Workspace helper and keeps stale-agent pruning scan-backed; no timing, lock, observer, or split-lifecycle workaround added.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed Diff only touched session-restore/persistence code; no NSWindow/NSPanel/WindowGroup changes or cmuxAuxiliaryWindowIdentifiers edits, so the rule doesn’t apply.
Cmux Source Artifacts ✅ Passed All changed paths are hand-written source files under Sources/; no artifact, cache, log, temp, or build-output paths appear in the diff.
Cmux No Test Or Debug Seam In Production Source ✅ Passed PASS: Diff adds production auto-resume helpers used by Workspace/DockSplitStore; no #if DEBUG/test-only accessor or wrapper seam was introduced in changed Sources files.
Title check ✅ Passed The title clearly matches the main change: preserving restored agent resume bindings.
Description check ✅ Passed The description covers summary, verification, and notes, with only optional template sections left unfilled.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-restored-agent-resume-bindings

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 24, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a bug where agent-hook resume bindings were being eagerly cleared from workspace state when a restored panel entered prompt-idle reconciliation, causing auto-resume to silently fail on the next relaunch. Stale binding pruning is preserved but now correctly deferred to the liveness scan path.

  • Core fix (Workspace+AgentLifecycle.swift): Removes the three-line block that called surfaceResumeBindingsByPanelId.removeValue(forKey:) on every promptIdle transition for agent-hook bindings; the binding now persists until reconcileSurfaceResumeBindings clears it on a liveness scan with no matching live process.
  • Shared auto-resume policy (Workspace.swift, callers in DockSplit*): Extracts a nonisolated static func shouldAutoResumeRestoredAgent(autoResumeAgentSessions:wasAgentRunning:resumeBinding:) used consistently across the three restore/snapshot paths, extending auto-resume to bindings that have structured checkpoint identity (hasAutoRestorableAgentCheckpointIdentity) even when wasAgentRunning == false.
  • Regression coverage: Adds tests for the preserve-then-prune lifecycle, same-surface/checkpoint liveness matching, and the resumed-but-exited binding case.

Confidence Score: 5/5

Safe to merge. The change is a targeted three-line removal in updateBindingOnlyRestoredAgentResumeState, a pure nonisolated helper extraction, and a new computed property; all pruning still executes via reconcileSurfaceResumeBindings on the liveness-scan path.

The fix correctly narrows the eager clear to only happen via the liveness scan, which has the live-process check the eagerly removed code was bypassing. The shouldAutoResumeRestoredAgent helper is pure and nonisolated, consolidating previously duplicated inline logic across three call sites. All three are updated consistently. The regression tests exercise the full preserve-then-prune lifecycle and the same-surface/same-checkpoint liveness matching, covering the key invariants. No actor isolation issues, no ambient global state, no test seams added to production source.

Files Needing Attention: No files require special attention.

Important Files Changed

Filename Overview
Sources/Workspace+AgentLifecycle.swift Removes eager agent-hook binding clear from the promptIdle handler in updateBindingOnlyRestoredAgentResumeState; binding lifecycle now deferred to liveness scan.
Sources/SessionPersistence.swift Adds hasAutoRestorableAgentCheckpointIdentity computed property to SurfaceResumeBindingSnapshot; guards correctly on source, autoResume, non-empty checkpointId, and a valid RestorableAgentKind.
Sources/Workspace.swift Extracts nonisolated static shouldAutoResumeRestoredAgent helper and updates the restoreSessionSnapshot call site to use it; correctly moves agentWasRunningAtQuit calculation to the new helper.
Sources/DockSplitStore+SessionRestore.swift Caller updated to delegate to Workspace.shouldAutoResumeRestoredAgent; no logic change beyond aligning with shared helper.
Sources/DockSplitStore+SessionSnapshot.swift Caller updated to delegate to Workspace.shouldAutoResumeRestoredAgent; no logic change beyond aligning with shared helper.
cmuxTests/WorkspaceIsStaleAgentHookBindingTests.swift Adds kind to factory, adds regression test for binding preservation through promptIdle, and adds liveness-matching tests for same-surface/same-checkpoint requirements.
cmuxTests/AgentSessionAutoResumeSettingsTests.swift Renames and rewrites one test to reflect the new behavior (agent-hook binding with exited agent now auto-resumes), adds a second test for nil wasAgentRunning state.
cmuxTests/AgentSessionAutoResumeSwiftTests.swift Adds assertions showing binding survives promptIdle in the workspace state and is correctly cleared only after reconcileSurfaceResumeBindings runs with an empty index.

Reviews (2): Last reviewed commit: "Allow trusted hook checkpoints past stal..." | Re-trigger Greptile

surfaceResumeBindingIndex: .empty
)
#expect(prunedSnapshot.panels.first?.terminal?.resumeBinding == nil)
#expect(restored.sessionSnapshot(includeScrollback: false).panels.first?.terminal?.resumeBinding == nil)

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 Final assertion silently depends on preceding side effect

sessionSnapshot(includeScrollback: false) (no explicit indexes) does NOT call reconcileSurfaceResumeBindings, so it cannot prune the binding on its own. This == nil assertion only holds because the prunedSnapshot call at the preceding lines already invoked reconcileSurfaceResumeBindings(using: .empty) as a side effect and removed the binding from surfaceResumeBindingsByPanelId. If someone later reorders or removes the prunedSnapshot block, this line will silently flip from documenting the correct new behavior back to testing the old (now-deleted) eager-clear path — and it would pass for the wrong reason until a real scenario exercises the difference. A short comment above the assertion (e.g. // binding removed from workspace state by liveness-scan side effect above) would make the dependency explicit.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

autoResume: Bool? = nil
) -> SurfaceResumeBindingSnapshot {
SurfaceResumeBindingSnapshot(
command: "claude --resume session-1",

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 kind addition silently fixes existing tests

Adding kind: "claude" to the factory changes the behavior of the two pre-existing tests. isStaleAgentHookBinding guards on binding.kind being non-nil and non-empty; without kind, the guard exits false regardless of liveness, so localAgentHookBindingWithNoLiveProcessIsStale would have been returning the wrong value before this change. The fix is correct, but it would be worth a brief note in the commit or test comment that the kind field is required for isStaleAgentHookBinding to reach the liveness check — this prevents a future refactor of the factory from silently re-introducing the gap.

@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 `@cmuxTests/AgentSessionAutoResumeSettingsTests.swift`:
- Around line 387-390: The policy tests should stop inspecting generated
launcher script text and instead verify observable binding-policy outcomes and
restored state. In cmuxTests/AgentSessionAutoResumeSettingsTests.swift lines
387-390, replace the scriptContains assertion with binding-policy and
restored-state assertions; at lines 442-445, assert the unknown-state policy
outcome. Keep launcher shell behavior assertions exclusively in
AgentResumeReturnShellStartupTests.swift.

In `@Sources/DockSplitStore`+SessionSnapshot.swift:
- Around line 136-142: Before the autoResumeAgentSessions eligibility call in
the Dock snapshot restore flow, scan the supplied indexes for a live matching
agent and clear effectiveSessionResumeBinding when none exists, matching
Workspace’s stale-binding pruning behavior. Ensure the override cannot convert a
stale local binding into startup work, and add a Dock snapshot regression
covering empty indexes and a dead checkpoint.
🪄 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 Plus

Run ID: 4cd320d9-c2c5-41f3-88d4-73cf26759161

📥 Commits

Reviewing files that changed from the base of the PR and between c09344f and 737e2f2.

📒 Files selected for processing (6)
  • Sources/DockSplitStore+SessionRestore.swift
  • Sources/DockSplitStore+SessionSnapshot.swift
  • Sources/SessionPersistence.swift
  • Sources/Workspace.swift
  • cmuxTests/AgentSessionAutoResumeSettingsTests.swift
  • cmuxTests/WorkspaceIsStaleAgentHookBindingTests.swift

Comment on lines +387 to +390
try assertAgentAutoResumeUsesStartupCommand(
restoredPanel,
scriptContains: ["codex resume codex-exited-binding-session"]
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Keep launcher-script assertions in the authoritative return-shell suite.

These policy tests now inspect generated script text, coupling them to launcher implementation details.

  • cmuxTests/AgentSessionAutoResumeSettingsTests.swift#L387-L390: assert the binding-policy outcome and restored state rather than command-script contents.
  • cmuxTests/AgentSessionAutoResumeSettingsTests.swift#L442-L445: assert the unknown-state policy outcome; keep shell behavior in AgentResumeReturnShellStartupTests.

Based on learnings, AgentResumeReturnShellStartupTests.swift is the authoritative auto-resumed return-shell contract; prefer observable shell behavior over brittle launcher assertions here.

📍 Affects 1 file
  • cmuxTests/AgentSessionAutoResumeSettingsTests.swift#L387-L390 (this comment)
  • cmuxTests/AgentSessionAutoResumeSettingsTests.swift#L442-L445
🤖 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 `@cmuxTests/AgentSessionAutoResumeSettingsTests.swift` around lines 387 - 390,
The policy tests should stop inspecting generated launcher script text and
instead verify observable binding-policy outcomes and restored state. In
cmuxTests/AgentSessionAutoResumeSettingsTests.swift lines 387-390, replace the
scriptContains assertion with binding-policy and restored-state assertions; at
lines 442-445, assert the unknown-state policy outcome. Keep launcher shell
behavior assertions exclusively in AgentResumeReturnShellStartupTests.swift.

Source: Learnings

Comment on lines +136 to +142
autoResumeAgentSessions: Workspace.shouldAutoResumeRestoredAgent(
autoResumeAgentSessions: AgentSessionAutoResumeSettings.isEnabled(
defaults: agentSessionAutoResumeDefaults
),
wasAgentRunning: agentWasRunning,
resumeBinding: resumeBinding
),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Prune stale local agent-hook bindings before granting this override.

When the supplied indexes contain no matching live agent, sessionAgentWasRunning becomes false, but effectiveSessionResumeBinding still retains the stored binding. This new call then turns that stale local binding into startup work, so a later Dock restore resumes a dead checkpoint. Apply the same scan-backed stale-binding pruning as Workspace before this eligibility check, and add a Dock snapshot regression using empty indexes.

As per coding guidelines, persistence paths must handle stale cached state and correctness-critical state must use a reliable source of truth.

🤖 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/DockSplitStore`+SessionSnapshot.swift around lines 136 - 142, Before
the autoResumeAgentSessions eligibility call in the Dock snapshot restore flow,
scan the supplied indexes for a live matching agent and clear
effectiveSessionResumeBinding when none exists, matching Workspace’s
stale-binding pruning behavior. Ensure the override cannot convert a stale local
binding into startup work, and add a Dock snapshot regression covering empty
indexes and a dead checkpoint.

Source: Coding guidelines

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

Labels

stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants