Skip to content

fix(ssh): keep local shells local when restoring a preserved SSH TUI workspace - #14238

Closed
teamleaderleo wants to merge 2 commits into
manaflow-ai:mainfrom
teamleaderleo:fix/ssh-tui-restore-keeps-local-shells
Closed

teamleaderleo wants to merge 2 commits into
manaflow-ai:mainfrom
teamleaderleo:fix/ssh-tui-restore-keeps-local-shells

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Sep 24, 2026 •

Copy link
Copy Markdown
Collaborator

Restoring a preserved SSH workspace turned every local shell inside it into a disconnected remote mirror.

Cause

5f0d222 (#13866) added this to Workspace.createPanel:

if usesSSHTui, remoteConfiguration?.preserveAfterTerminalExit == true, snapshot.type == .terminal {
    return restoreDeviceDisplayPanel(snapshot, in: paneId)
}

It catches every terminal snapshot in the workspace, including a local shell saved with isRemoteTerminal == false. That shell comes back as a device mirror with no local process and no working directory. testPersistentSSHPTYRestorePreservesLocalTerminalWorkingDirectory caught it; #14222 parked it behind a skip.

Fix

  • Restore: the branch now also requires cloudProjectionRecord != nil || snapshot.terminal?.isRemoteTerminal != false. Projected SSH terminals are unaffected: they restore through reserveRestoredCloudTerminalPane before this branch. Snapshots from before isRemoteTerminal existed (nil) still restore as mirrors. A terminal saved as local, with no projection, restores through the normal local path, and no remote startup or attach command is added (effectiveRemoteTerminalStartupCommand is nil for a TUI-owned persistent config, and tuiSSHConfiguration sets no daemon slot).
  • Save: a cmux-tui mirror pane can be saved before its projection exists (a reservation, respawn, or failed creation). It used to be saved with isRemoteTerminal == false, which the narrowed check would read as a local shell. Manual-mirror panes in an SSH TUI workspace are now saved as remote. Every live mirror pane previously saved false, so the remaining readers of the flag (crash-storage scrollback, sidebar directory trust) now treat mirrors as remote, which matches what they are.

testPersistentSSHPTYRestorePreservesLocalTerminalWorkingDirectory is re-enabled.

Known gap, not a regression: in a workspace that restores with a cmux-tui cloudVMBinding, restoresUntrustedSavedDirectory still drops the local shell's saved directory, so it opens in the default directory. Before this PR it didn't come back as a shell at all.

🤖 Generated with Claude Code

…workspace

Since 5f0d222, createPanel sent every terminal snapshot in a
preserved SSH TUI workspace to restoreDeviceDisplayPanel, so a local
shell saved in that workspace came back as a disconnected remote mirror
and lost its shell and working directory. Projected SSH terminals carry a
surface projection record in the snapshot and local shells do not, so
restore as a device mirror only when there is a projection record or the
snapshot was not saved as a local terminal.

Re-enables testPersistentSSHPTYRestorePreservesLocalTerminalWorkingDirectory.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 2 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: 46275970-8f9a-48c6-ade0-2627a961774b

📥 Commits

Reviewing files that changed from the base of the PR and between cd7a4cf and 3ec4c1c.

📒 Files selected for processing (2)
  • Sources/Workspace.swift
  • cmuxTests/TabManagerSessionSnapshotTests.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.

A mirror pane can be saved before its projection exists (a reservation,
respawn, or failed creation). It was saved with isRemoteTerminal false
and no projection record, so the narrowed restore check would have
brought it back as a local shell. Save manual-mirror panes in an SSH TUI
workspace as remote, and correct the restore comment: projected SSH
terminals restore through reserveRestoredCloudTerminalPane before this
branch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@teamleaderleo
teamleaderleo enabled auto-merge (squash) September 24, 2026 15:49
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Closing: #14119 retired testPersistentSSHPTYRestorePreservesLocalTerminalWorkingDirectory and now restores legacy persistent SSH snapshots as a blocked descriptor, and SSHTuiWorkspaceCoordinator is built on the rule that an SSH workspace never starts a local shell. Given that, restoring every terminal in a preserved SSH TUI workspace as a mirror is the intended design rather than a regression, so this change would work against it.

auto-merge was automatically disabled September 24, 2026 15:51

Pull request was closed

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