Skip to content

Recover routed Claude sessions through cmux restore; journal closed panes - #15324

Merged
teamleaderleo merged 3 commits into
mainfrom
agent-recovery-restore-path
Sep 28, 2026
Merged

teamleaderleo merged 3 commits into
mainfrom
agent-recovery-restore-path

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Sep 28, 2026 •

Copy link
Copy Markdown
Collaborator

Follow-up to #14870.

Crash recovery typed its own launcher command for Claude sessions started through a routed launcher. That skipped three things a normal cmux restore does: it never set CMUX_AGENT_RESTORE_LAUNCH or CMUX_CUSTOM_CLAUDE_PATH for the wrapper, it never checked that the routed launcher is on PATH, and it dropped the permission mode the session was last observed in.

Recovery now routes those sessions through the regular restore path. Each recovered session opens in a workspace whose terminal carries the session as its startup restore agent (the mechanism Vault resume uses), and types cmux restore claude <id>. The CLI reads that surface's restore record and plans with the same AgentRestorePlanner a normal restore uses, so wrapper authorization, the PATH check with fallback to a direct resume, and the permission mode come from one code path. Plain Claude sessions take the same path. The launch claim is taken at the CLI's admission boundary, as in a normal restore. Only a session started through an undeclared launcher that the restore record cannot rebuild still resumes through its recorded prefix, and that command now reapplies the observed permission mode too.

When the routed launcher is missing, the planner already fell back to a direct resume silently. It now returns a notice, and cmux restore prints the reason on stderr before starting the agent: "this session was started through the routed launcher 'sr', which is not on PATH. Resuming the agent directly instead." The string is localized in all 9 required locales.

Closing a Claude pane kills the agent before its own end hook reports, so the journal kept the session open and a later crash reopened a pane the user had closed. Closing a panel now journals agent.session.ended for the Claude session it carried (its agent-hook binding, restored snapshot, or deferred restore). It skips panels moved to another workspace and closes during app quit, where startup restore owns the sessions. Dock panels are not covered yet.

Testing

Regression test, two commits: cmuxTests/AgentSessionRecoveryAppTests/closedClaudePaneIsNotRecoveredAfterACrash() closes a pane carrying a Claude session in a real Workspace, waits for the journal write, then runs recovery against the same journal and hook store as the next launch after a crash. Only the session that died with the app may come back. Commit 1 (red, ba10d4f) adds the test and the journal seam without wiring it into the close path. Commit 2 adds the fix. CI receipts are posted below.

Also added: routedSessionsResumeThroughTheRestoreVerb (app host), package tests that a routed candidate yields no launcher argv, that the launcher command reapplies the observed permission mode unless the launch pinned one, that the planner carries the mode, and that a missing routed launcher produces the notice.

Nothing was compiled or run on the author's machine beyond the localization check (localization_catalog.py check: 0 parity errors) and the pbxproj check. Compiles and suites run in CI.

— Icicle g1 ⚙️ (run_worker_20260927_686a3a99)

🤖 Generated with Claude Code


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

Recovers routed Claude sessions through the normal cmux restore path and journals closed Claude panes so crash recovery no longer reopens sessions the user closed.

Bug Fixes

  • Recovery types cmux restore claude <id> from a panel carrying the session's restore record, so routed sessions now get wrapper authorization, a PATH check, and the observed permission mode like a normal restore.
  • A missing routed launcher falls back to a direct resume and now prints why on stderr; the message is localized in all 9 locales.
  • Closing a Claude pane journals agent.session.ended for that session (skipped for panels moved between workspaces, sessions another panel still carries, and during app quit), so a later crash recovery won't reopen it.

Written for commit 05bf5cb. Summary will update on new commits.

Review in cubic

teamleaderleo and others added 2 commits September 28, 2026 06:40
Closing a Claude pane kills the agent before its SessionEnd hook runs, so
the journal keeps the session open and crash recovery reopens it. The
test closes a pane carrying a Claude session, then runs recovery against
the same journal and hook store; the closed session must not be a
candidate. Adds the journal seam the test injects; the close path does
not use it yet, so this commit fails.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…d panes

Crash recovery typed its own launcher command for routed Claude
sessions, so it skipped three things the normal restore does: setting
CMUX_AGENT_RESTORE_LAUNCH and CMUX_CUSTOM_CLAUDE_PATH for the wrapper,
checking the routed launcher is on PATH, and reapplying the observed
permission mode. Recovery now opens each session in a panel carrying its
restore record and types `cmux restore claude <id>`, so the planner the
normal restore uses covers all three. Only an undeclared launcher that
the restore record cannot rebuild still resumes through its recorded
prefix, now with the observed permission mode.

When the routed launcher is missing, the restore planner already fell
back to a direct resume; it now reports why on stderr.

Closing a pane records agent.session.ended for the Claude session it
carried, so a crash afterwards no longer reopens it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@cursor

cursor Bot commented Sep 28, 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.

@coderabbitai

coderabbitai Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 11 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 10 included reviews currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Repository: manaflow-ai/cmux/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 4f00140c-3071-4816-87d5-e82779e57c60

📥 Commits

Reviewing files that changed from the base of the PR and between 1755ea8 and 05bf5cb.

📒 Files selected for processing (14)
  • CLI/CMUXCLI+AdmittedRestore.swift
  • Packages/macOS/CMUXAgentLaunch/Sources/CMUXAgentLaunch/AgentRestoreInvocation.swift
  • Packages/macOS/CMUXAgentLaunch/Sources/CMUXAgentLaunch/AgentRestoreNotice.swift
  • Packages/macOS/CMUXAgentLaunch/Sources/CMUXAgentLaunch/AgentRestorePlanner.swift
  • Packages/macOS/CMUXAgentLaunch/Sources/CMUXAgentLaunch/AgentSessionRecoveryPlanner.swift
  • Packages/macOS/CMUXAgentLaunch/Tests/CMUXAgentLaunchTests/AgentSessionRecoveryTests.swift
  • Packages/macOS/CMUXAgentLaunch/Tests/CMUXAgentLaunchTests/SubrouterClaudeRestoreRoutingTests.swift
  • Resources/Localizable.xcstrings
  • Sources/AgentSessionCloseJournal.swift
  • Sources/AgentSessionRecovery.swift
  • Sources/Workspace+PanelLifecycle.swift
  • Sources/Workspace.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/AgentSessionRecoveryAppTests.swift

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.

@github-actions

Copy link
Copy Markdown
Contributor

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

@github-actions

github-actions Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Dogfood build of 05bf5cb0c3e9c5604ea43c1eefa0e87f5ccba191

cmux DEV pr-15324-05bf5cb0.app

The link opens this exact commit in the cmux dev menu bar app. The build starts on each push and the page waits until it is ready; a newer push replaces it. It signs in against production, so Cloud or backend changes still need a tagged build with a development backend.

…ions carried elsewhere

A closed panel no longer journals the end of a session another panel
still carries, such as a restore that lost to a live owner.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@cursor

cursor Bot commented Sep 28, 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 28, 2026 •

Copy link
Copy Markdown
Contributor

CI failure attribution

CI passes on 05bf5cb0c3 (run 36412171293 attempt 2).

Written by scripts/ci/classify_failures.py (ci-failure-attribution.yml); signatures are its SIGNATURES table. A machine verdict is the runner's fault, not this PR's.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Regression proof for cmuxTests/AgentSessionRecoveryAppTests/closedClaudePaneIsNotRecoveredAfterACrash():

Subagent review: the first pass found one blocker (a stale --resume assertion for plain sessions) and a should-fix (closing a panel could end a session another panel still carries). Both are fixed in 05bf5cb, and a second pass found no issues. Deferred to #15363: pacing recovered launches, account-pin capture when a routed launcher appends arguments, and Dock close journaling. Residual from the #14870 review: a pane the user closed during the run no longer comes back even if Claude records no end on SIGHUP, because the app journals the close itself (Workspace panels; Dock tracked in the issue).

Not verified: no tagged-build GUI dogfood. Coverage is the app-host and package tests above.

— Icicle g1 ⚙️ (run_worker_20260927_686a3a99)

@teamleaderleo
teamleaderleo merged commit 31b2619 into main Sep 28, 2026
108 of 112 checks passed
@teamleaderleo
teamleaderleo deleted the agent-recovery-restore-path branch September 28, 2026 12:41
@github-actions

Copy link
Copy Markdown
Contributor

Merge receipt for 05bf5cb0c3: every check was green at merge (24 verified; 16 skipped by policy). Full suite runs on main after merge.

rustybret pushed a commit to rustybret/bmux that referenced this pull request Sep 28, 2026
7b6ad14 PR media: tour-only pushes load the earlier build of the same inputs (manaflow-ai#15361)
842d580 ci: prebuild selected Swift packages in parallel before the serial test pass (manaflow-ai#15114)
1a7467a dogfood: give the agent activity reorder tour paths globs (manaflow-ai#15360)
31b2619 Recover routed Claude sessions through cmux restore; journal closed panes (manaflow-ai#15324)
5a3ca1a ci: price mini contention in distance routing and keep SwiftPM builds on owned Macs (manaflow-ai#14804)
b8afe20 Add notifications.suppressWhenAppFocused setting (manaflow-ai#14701)
3843dd6 ci: queue main's whole full-suite run on the owned pool (manaflow-ai#15356)

# Conflicts:
#	.github/workflows/ci-guards.yml
#	.github/workflows/ci-macos.yml
#	.github/workflows/pr-media.yml
#	.github/workflows/test-e2e.yml
teamleaderleo added a commit that referenced this pull request Sep 28, 2026
…s own, journal Dock closes (#15375)

* Regression tests: paced crash recovery, recorded account pin, Dock close journaling

Three leftovers from crash recovery (#15363), each pinned by a test that
fails on main:

- Recovery starts every lost session at once. The tests expect only a few
  to start now (sessions from a workspace on screen, then the most recently
  active) and the rest to open their workspace and start on first visit.
  Adds the start plan seam, which still starts everything.
- A routed launcher that appends arguments after the forwarded tail leaves
  no launcher prefix, so the resumed session lost its account pin. The
  tests expect the wrapper to record the pin from the launcher's own
  routing headers and the routed restore to use it.
- Closing a Claude pane in the Dock does not journal agent.session.ended.
  Adds the Dock's journal seam; the close path does not use it yet.

Refs #15363

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Start only a few recovered agent sessions at once

Crash recovery started every lost session together, so a heavy user's
relaunch spawned dozens of agents at the same moment. Recovery now starts
a few right away: sessions from a workspace on screen, then the most
recently active. The rest open their workspace now and resume on its first
visit, the way startup restore treats background workspaces. Their panels
carry the session from the start, so a second recovery still skips them.

A session resumed through its recorded launcher still starts now, since
its launch claim is taken when the command is typed.

Refs #15363

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Record the routed launch's account pin on its own

The pin a routed Claude restore reapplies came from the launcher prefix,
which is what is left of the launcher's argv after stripping the tail it
forwarded to Claude. A routed launcher that appends arguments after that
tail breaks the match, so no prefix was recorded and the resumed session
went back to the pool.

The wrapper now records the pinned account itself. It reads the pin from
the routing headers the launcher wrote into its private settings file for
this launch, so the pin no longer depends on how the launcher was invoked.
The hook keeps it with the launch record, and the routed restore passes it
as `--account`. A prefix pin still applies to records that predate this.
The value is launch metadata only and is never replayed into a resumed
process's environment.

Refs #15363

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Journal Dock pane closes through the shared close path

Closing a workspace pane records agent.session.ended for the Claude session
it carried (#15324), but a Dock pane close did not, so a Claude pane closed
in the Dock could come back after a crash.

The close journaling moves off Workspace onto a small protocol both panel
owners conform to, and the Dock calls it from the teardown every close
takes. A panel the Dock hands to another container is detached first and
is not journaled.

Refs #15363

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Address review: hold deferred recoveries until visit; carry the pin through queued hooks

A deferred recovered session still started at once: a panel that carries a
restore record is admitted when its workspace commits, and admission starts
the terminal headless whether or not the workspace is loaded. Recovery now
stages those panels with admission deferred, and the workspace admits them
when it is first selected. The app test checks the held terminals and the
release on visit.

The wrapper's recorded account pin never reached the session-start capture:
Claude's lifecycle hooks are queued, and each queued layer keeps only an
allowlist of environment keys. Claude's queued hooks now carry the routed
launch metadata (the pin and the marker pair), and the app's hook ingress
accepts it. The capture still validates each value before recording it.

Refs #15363

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Pass the first-visit hold through workspace creation

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Address review: journal the Dock's aside agent-hook binding on close

When Dock process detection shows a tmux binding, the agent-hook binding
waits in managedAgentResumeBindingsByPanelId, and a close read only the
effective one. The host now lists every binding that can name the session.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Fix workspace creation test overrides

* Test carried Dock sessions survive stale pane close

* Keep managed Dock sessions open during stale close

* Fix workspace create review test override

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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