Skip to content

Make Cloud VM SSH sessions show reconnect state - #3899

Closed
austinywang wants to merge 29 commits into
mainfrom
issue-3776-cloud-vm-ssh-resilience
Closed

austinywang wants to merge 29 commits into
mainfrom
issue-3776-cloud-vm-ssh-resilience

Conversation

@austinywang

@austinywang austinywang commented May 12, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Verification

  • Reproduced locally with a mocked SSH startup command: transient SSH exit 255 retried but only emitted final ssh-session-end before the fix
  • Added failing regression test commit first: 9641d8a
  • Added fix commit second: c087e7a
  • Ran: git diff --check
  • Ran: python3 -m json.tool Resources/Localizable.xcstrings

Not run

  • Local test suite, per repo instruction; CI will run tests
  • Dev app build/launch, per task order, until CI is fully green

Note

Medium Risk
Touches SSH startup wrapper scripting and remote-workspace state transitions; mistakes could cause incorrect connection state in the UI or missed/dropped lifecycle events during reconnects.

Overview
Cloud VM SSH panes now emit explicit lifecycle events during transient SSH drops: the startup wrapper reports reconnecting (with attempt/limit) on exit-255 retries and connected after a successful redial, while keeping the final ssh-session-end cleanup separate.

This adds internal CLI commands ssh-session-reconnecting/ssh-session-connected, new RPC handlers (workspace.remote.terminal_reconnecting/terminal_connected), and Workspace logic to apply these updates per-surface using a monotonic sequence to ignore stale/out-of-order signals and to settle the workspace state correctly after the last terminal ends (including browser-panel and VM no-proxy cases). Localization strings, CLI contract docs, and targeted regression tests were added/updated accordingly.

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


Summary by cubic

Cloud VM SSH sessions now correctly reflect reconnect state through the native CloudTuiManualMirrorSession lifecycle (reconnecting/attached/ended) instead of the earlier custom status RPCs; transient drops stay silent and persistent failures surface a Reconnect action.

  • Refactors
    • Removed the per-surface sequence helpers and new CLI/RPC commands ssh-session-reconnecting, ssh-session-connected, workspace.remote.terminal_reconnecting, and workspace.remote.terminal_connected.
    • Aligns SSH terminal lifecycle events with the native session owner's attempt IDs and stale-attachment fencing, preserving existing recovery tests.

Written for commit 983f338. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Emit new remote terminal lifecycle notifications for "reconnecting" and "connected" with localized status messages.
    • UI now reflects reconnect/connect transitions for individual remote terminal sessions.
  • Bug Fixes

    • More robust workspace remote-state settling after the final terminal session ends to avoid incorrect disconnect behavior.
  • Documentation

    • Added cloud VM reconnect smoke test and updated CLI contract docs.
  • Tests

    • Added/expanded tests covering SSH reconnecting, connected, and session-end ordering and state handling.

Review Change Stack

The startup wrapper already retries transient SSH exits, but no regression coverage proves that the foreground transport reports retry and recovery transitions back to the app-owned workspace state. This test captures the sleep/network-drop path by making SSH exit 255 once, then requiring reconnecting and connected lifecycle helper calls before final session end.

Constraint: Local tests are intentionally not run for this repo; CI owns execution.

Rejected: Assert only terminal stderr reconnect text | acceptance requires the Cloud VM pane/app state to surface reconnecting, not just shell output.

Confidence: medium

Scope-risk: narrow

Tested: Not run locally per instruction; expected to fail before the fix because the lifecycle helper commands do not exist yet.

Not-tested: CI execution pending after fix commit.
The foreground SSH wrapper is the only process that can observe an interactive transport drop, while Workspace owns the visible remote state. The fix adds explicit terminal lifecycle helpers for reconnecting and connected transitions, routes them through TerminalController RPCs, and lets Workspace translate them into the existing remote connection state model.

Constraint: Do not infer foreground SSH health from daemon/proxy failures; the terminal process owns foreground transport observations.

Constraint: No local test runs for this repo; CI owns test execution.

Rejected: Reuse ssh-session-end for retries | final-session cleanup would demote or clear state instead of preserving the active remote terminal during retry.

Rejected: Poll the remote controller for foreground drops | proxy/daemon state is a different lifecycle and misses Cloud VM SSH-only fallback sessions.

Confidence: medium

Scope-risk: moderate

Tested: git diff --check; python3 -m json.tool Resources/Localizable.xcstrings

Not-tested: Local test suite per instruction; CI pending.
@vercel

vercel Bot commented May 12, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment May 19, 2026 6:52am
cmux-staging Building Building Preview, Comment May 19, 2026 6:52am

@coderabbitai

coderabbitai Bot commented May 12, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds SSH session reconnecting/connected lifecycle end-to-end: CLI emits lifecycle commands, new IPC subcommands parse and forward validated params, TerminalController exposes V2 RPCs and applies unified lifecycle payloads, Workspace updates remote connection state, tests and localized strings/docs added.

Changes

Remote Terminal Reconnect Lifecycle

Layer / File(s) Summary
CLI lifecycle command generation and reconnect script
CLI/cmux.swift
Emit ssh-session-reconnecting/ssh-session-connected lifecycle commands from the generated SSH startup/reconnect loop and add shared lifecycle shell-command builders.
CLI IPC handlers and parsing
CLI/cmux.swift
Add runSSHSessionReconnecting and runSSHSessionConnected handlers and refactor runSSHSessionEnd to share lifecycle identity parsing and integer option validation.
V2 RPC dispatcher and capability wiring
Sources/TerminalController.swift
Register workspace.remote.terminal_reconnecting and workspace.remote.terminal_connected in the JSON-socket dispatcher and advertise them in v2Capabilities().
Shared remote-terminal lifecycle validation and apply helper
Sources/TerminalController.swift
Introduce RemoteTerminalLifecycleIdentity and v2ApplyWorkspaceRemoteTerminalLifecycle to centralize parsing/validation and construct standardized success/error payloads for lifecycle events.
Workspace remote connection state management
Sources/Workspace.swift
Refactor last-terminal-exit demotion into settleRemoteConnectionStateAfterLastTerminalSessionEnded(), add markRemoteTerminalSessionReconnecting and markRemoteTerminalSessionConnected, and gate updates by matching surface/relay identity.
Integration and unit tests
cmuxTests/*
Add tests verifying reconnect → connected ordering and metadata, error-state preservation, demotion when reconnecting sessions end, and browser-panel variants; add SSH startup lifecycle regression test.
Localization strings and documentation
Resources/Localizable.xcstrings, docs/cli-contract.md, docs/remote-daemon-spec.md
Add English/Japanese localization entries for reconnecting/terminal-ended/target fallback, add CLI contract entries for new subcommands, and document a manual Cloud VM sleep/network reconnect smoke test.

Sequence Diagram

sequenceDiagram
  participant CLI as CLI/cmux
  participant TerminalController as Sources/TerminalController
  participant Workspace as Sources/Workspace

  CLI->>TerminalController: invoke `ssh-session-reconnecting` / `ssh-session-connected` (validated args)
  TerminalController->>Workspace: v2ApplyWorkspaceRemoteTerminalLifecycle(apply reconnect/connected/session-end)
  Workspace->>Workspace: validate surface/relay & update remoteConnectionState
  Workspace-->>TerminalController: success payload (window/workspace/surface/relay_port/status)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • manaflow-ai/cmux#3995: Touches CLI/cmux.swift lifecycle emission and pane-exit handling; code-level related to lifecycle command wiring.

"I'm a rabbit in a shell so small,
I hop through scripts and RPC call,
Reconnecting beats the thunder's roar,
Connected now — the session's more.
Hooray for logs, and tests that sprawl!"


Caution

Pre-merge checks failed

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

  • Ignore

❌ Failed checks (2 errors, 1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Cmux Full Internationalization ❌ Error Three new user-facing localization keys added with only 2 locales (en, ja) when catalog supports 19. Missing translations for 17 locales violates full-internationalization rule. Add translations or English fallback entries for all 17 missing locales (ar, bs, da, de, es, fr, it, ko, nb, pl, pt-BR, ru, th, tr, uk, zh-Hans, zh-Hant) to the three new string keys in Resources/Localizable.xcstrings.
Cmux Architecture Rethink ❌ Error markRemoteTerminalSessionReconnecting() can regress .connected state. No guard prevents stale callbacks. Missing lifecycle generation or phase tracking allows out-of-order event vulnerability. Add guard: guard remoteConnectionState != .connected else { return }. Add lifecycle generation counter to reject stale events.
Docstring Coverage ⚠️ Warning Docstring coverage is 4.65% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The pull request description covers the summary, verification steps, and includes auto-generated overviews, but lacks explicit demonstration of manual testing with demo content and does not clearly map to all template sections. Clarify whether a demo video is applicable (likely not for this internal CLI change); explicitly confirm that the 'I tested the change locally' checklist item was completed and provide more detail on verification scope.
✅ Passed checks (13 passed)
Check name Status Explanation
Linked Issues check ✅ Passed All four acceptance criteria from #3776 are met: reconnecting state display [sources show workspace UI updates], automatic reconnect [CLI subcommands and RPC handlers], session state clarity [localized messages and settled states], and documented smoke test [docs/remote-daemon-spec.md T-006].
Out of Scope Changes check ✅ Passed All changes directly support the PR objectives: SSH lifecycle event handling, RPC handlers, workspace state updates, localization, tests, and documentation are all in scope for #3776.
Cmux Swift Actor Isolation ✅ Passed Workspace methods marked @MainActor. TerminalController handlers use @MainActor closures. Tests marked @MainActor. No isolation violations.
Cmux Swift Blocking Runtime ✅ Passed PR does not introduce blocking synchronization in production code. New lifecycle handlers use non-blocking state updates and async RPC calls. Use of v2MainSync is a pre-existing pattern.
Cmux No Hacky Sleeps ✅ Passed The custom check targets TypeScript, JavaScript, shell, and build/runtime scripts. This PR modifies only Swift code, localization, and documentation—no targeted code types.
Cmux Swift Concurrency ✅ Passed No legacy async patterns introduced. All new code is synchronous: CLI RPC callers, V2 handlers, and @MainActor Workspace methods. No Tasks, DispatchQueues, Combine usage, or completion-handlers.
Cmux Swift @Concurrent ✅ Passed All new Swift code follows @MainActor and concurrency rules correctly. No @concurrent misuse, proper MainActor isolation patterns used, all Workspace methods properly annotated.
Cmux Swift File And Package Boundaries ✅ Passed No new files created; all additions <250 lines to existing files; code coherent with existing responsibilities; no mixed concerns; allowed exception applies.
Cmux Swift Logging ✅ Passed No logging violations detected. New SSH lifecycle handlers contain no print/debug/NSLog, no file/stdout logging, no MainActor Logger constants, and no sensitive data exposure.
Cmux User-Facing Error Privacy ✅ Passed User-facing strings do not expose sensitive data. Localization strings are generic. CLI/RPC errors use standard validation messages. Implementation correctly hides SSH exit status from users.
Cmux Swiftui State Layout ✅ Passed PR only modifies existing @Published properties. No new SwiftUI state patterns, GeometryReader violations, lazy collection store refs, or render-time mutations detected.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR does not introduce or materially change any standalone cmux-owned windows, panels, or WindowGroups. Changes are limited to CLI subcommand handlers, RPC methods, and state management.
Title check ✅ Passed The title accurately summarizes the main change: adding reconnect state visibility to Cloud VM SSH sessions, which is the core objective of this PR.
✨ 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 issue-3776-cloud-vm-ssh-resilience

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 May 12, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR closes #3776 by adding transient SSH terminal lifecycle events — ssh-session-reconnecting (with attempt/limit/sequence) and ssh-session-connected — so Cloud VM SSH panes correctly report reconnecting and connected state transitions during retries, rather than staying silent until the final ssh-session-end. A per-surface monotonic sequence number prevents stale or out-of-order events from overwriting newer state, and settleRemoteConnectionStateAfterLastTerminalSessionEnded correctly handles the browser-panel vs. terminal-only demote paths when the last SSH terminal closes.

  • New CLI subcommands ssh-session-reconnecting and ssh-session-connected are routed through a shared buildSSHSessionLifecycleShellCommand builder that honours CMUX_BUNDLED_CLI_PATH at runtime, increments CMUX_SSH_LIFECYCLE_SEQUENCE before each SSH attempt in the retry loop, and passes it into the SSH LocalCommand environment for the connected signal.
  • Workspace gains three new @MainActor public lifecycle methods with sequence-acceptance guards; the existing untrackRemoteTerminalSurface / maybeDemoteRemoteWorkspaceAfterSSHSessionEnded helpers still lack @MainActor (tracked from the previous review round).
  • Six new unit tests cover reconnect-state transitions, error-state guard, exhausted-retry demote, browser-panel settle, VM no-proxy settle, and stale-sequence rejection; a new shell-level integration test exercises the real retry loop with fake ssh/cmux binaries and validates lifecycle ordering and sequence values.

Confidence Score: 5/5

Safe to merge; the new lifecycle paths are well-guarded and thoroughly tested, with the only remaining open item being a localization typography issue.

The core reconnect logic, sequence-guarding, and terminal settle paths are correct and covered by six new unit tests plus a shell integration test. The localization issue (missing diacritics in fr/es/nb/it/pt-BR) is user-visible but does not affect reconnect correctness. Pre-existing actor isolation gaps on two private helpers are already tracked from previous review rounds.

Resources/Localizable.xcstrings — the three new strings need diacritics corrected for fr, es, nb, it, and pt-BR before shipping to users in those locales.

Important Files Changed

Filename Overview
Sources/Workspace.swift Adds markRemoteTerminalSessionReconnecting/Connected (both @mainactor) with per-surface monotonic sequence acceptance; settleRemoteConnectionStateAfterLastTerminalSessionEnded (@mainactor) handles browser-panel vs. terminal-only path correctly; untrackRemoteTerminalSurface and maybeDemoteRemoteWorkspaceAfterSSHSessionEnded still lack @mainactor (pre-existing concern).
Sources/TerminalController.swift Routes two new RPCs to refactored v2ApplyWorkspaceRemoteTerminalLifecycle helper; apply closure is correctly annotated @mainactor; identity parsing extracted cleanly into a shared helper struct.
CLI/cmux.swift Adds ssh-session-reconnecting and ssh-session-connected subcommands; refactors all three lifecycle shell-command builders into a shared buildSSHSessionLifecycleShellCommand that correctly honours CMUX_BUNDLED_CLI_PATH at runtime, fixing the previous test-interception bug.
Resources/Localizable.xcstrings Adds three new localized strings with coverage across all supported locales; however fr, es, nb, it, and pt-BR are missing Unicode diacritics in their translations.
cmuxTests/WorkspaceRemoteConnectionTests.swift Adds six new @mainactor unit tests covering reconnect state, error-state guard, exhausted-retry demote, browser-panel settle, VM no-proxy settle, and stale-sequence rejection.
cmuxTests/SSHStartupSignalLifecycleTests.swift Adds integration test exercising a real shell script with fake ssh/cmux binaries; verifies reconnecting to connected to session-end ordering and correct sequence numbers without requiring a live app socket.
docs/cli-contract.md Documents the two new internal CLI subcommands in the command-family table; accurate and complete.
docs/remote-daemon-spec.md Adds T-006 smoke-test entry and a detailed Section 10.4 manual procedure covering reconnect, redial, and retry-exhaustion scenarios.

Sequence Diagram

sequenceDiagram
    participant Shell as SSH Wrapper (sh)
    participant CLI as cmux CLI
    participant TC as TerminalController
    participant WS as Workspace

    Shell->>Shell: "sequence++ to 1, export CMUX_SSH_LIFECYCLE_SEQUENCE=1"
    Shell->>Shell: launch ssh attempt 1
    Shell->>Shell: ssh exits 255 transient drop
    Shell->>Shell: "sequence++ to 2, export CMUX_SSH_LIFECYCLE_SEQUENCE=2"
    Shell->>CLI: ssh-session-reconnecting --sequence 2 --attempt 1
    CLI->>TC: "workspace.remote.terminal_reconnecting sequence=2"
    TC->>WS: "markRemoteTerminalSessionReconnecting(sequence=2)"
    WS->>WS: acceptSequence 2 gt 0 ok, state to reconnecting
    Shell->>Shell: "sequence++ to 3, export CMUX_SSH_LIFECYCLE_SEQUENCE=3"
    Shell->>Shell: launch ssh attempt 2
    Note over Shell: SSH connects, LocalCommand fires
    Shell->>CLI: ssh-session-connected --sequence 3
    CLI->>TC: "workspace.remote.terminal_connected sequence=3"
    TC->>WS: "markRemoteTerminalSessionConnected(sequence=3)"
    WS->>WS: acceptSequence 3 gt 2 ok, state to connected
    Shell->>Shell: ssh exits 0, wrapper exits
    Shell->>CLI: ssh-session-end
    CLI->>TC: workspace.remote.terminal_session_end
    TC->>WS: markRemoteTerminalSessionEnded()
    WS->>WS: untrackRemoteTerminalSurface, settle final state
Loading

Reviews (20): Last reviewed commit: "fix: localize ssh lifecycle fallback str..." | Re-trigger Greptile

Comment thread Sources/Workspace.swift Outdated
Comment thread Sources/Workspace.swift Outdated
Comment thread Sources/Workspace.swift
coderabbitai[bot]
coderabbitai Bot previously requested changes May 14, 2026

@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 `@docs/remote-daemon-spec.md`:
- Line 217: Add a blank line before the heading "### 10.4 Manual Cloud VM
Sleep/Network Smoke" in docs/remote-daemon-spec.md so the heading is preceded by
an empty line for consistent Markdown formatting; locate the heading text and
insert a single newline above it.
🪄 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: d824d734-a9d8-47a6-8242-d8abe3860533

📥 Commits

Reviewing files that changed from the base of the PR and between 6b0b97e and 7cb94ba.

📒 Files selected for processing (8)
  • CLI/cmux.swift
  • Resources/Localizable.xcstrings
  • Sources/TerminalController.swift
  • Sources/Workspace.swift
  • cmuxTests/SSHStartupSignalLifecycleTests.swift
  • cmuxTests/WorkspaceRemoteConnectionTests.swift
  • docs/cli-contract.md
  • docs/remote-daemon-spec.md

Comment thread docs/remote-daemon-spec.md Outdated
Comment thread Sources/Workspace.swift Outdated

@cubic-dev-ai cubic-dev-ai 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.

1 issue found across 1 file (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="Sources/Workspace.swift">

<violation number="1" location="Sources/Workspace.swift:9588">
P2: This new reconnecting guard forces a disconnect transition even when proxy/daemon readiness indicates the workspace should settle back to Connected.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread Sources/Workspace.swift Outdated
@austinywang
austinywang dismissed coderabbitai[bot]’s stale review May 18, 2026 08:58

Stale CodeRabbit changes-requested summary. The only actionable inline comment was fixed in df02c26, CodeRabbit acknowledged it, the thread is resolved, and the current CodeRabbit check is passing.

Comment thread Sources/Workspace.swift Outdated
coderabbitai[bot]
coderabbitai Bot previously requested changes May 19, 2026

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

Caution

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

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

9727-9728: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Clear pending child-exit surface IDs once the panel is truly gone.

remoteTerminalLifecycleMatches now treats pendingRemoteTerminalChildExitSurfaceIds as live, but in this file those IDs are only inserted here and removed again on trackRemoteTerminalSurface(_). After a terminal has actually closed, delayed lifecycle callbacks for that dead surface will still match and can mutate the workspace state long after the pane is gone. Clear the pending entry during panel/disconnect cleanup, or stop matching against the pending set here.

Also applies to: 9780-9788

🤖 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/Workspace.swift` around lines 9727 - 9728, The
pendingRemoteTerminalChildExitSurfaceIds set is left containing surfaceId
entries that are treated as live by remoteTerminalLifecycleMatches, allowing
delayed lifecycle callbacks to mutate state after a terminal pane is gone; when
you call untrackRemoteTerminalSurface(surfaceId) (or during the panel/disconnect
cleanup path) also remove that surfaceId from
pendingRemoteTerminalChildExitSurfaceIds so it no longer matches, or
alternatively stop consulting pendingRemoteTerminalChildExitSurfaceIds inside
remoteTerminalLifecycleMatches — update the cleanup code that runs when a panel
disconnects/actually closes (the same area that calls
untrackRemoteTerminalSurface and the panel/disconnect cleanup around the
9780–9788 changes) to clear
pendingRemoteTerminalChildExitSurfaceIds.remove(surfaceId).
🤖 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 `@docs/remote-daemon-spec.md`:
- Line 224: Update the reconnect smoke test expectation so it no longer requires
the raw SSH exit status to be visible: change the step that currently reads
"Verify the terminal pane and remote status payload enter `reconnecting`, with
detail that includes the SSH retry attempt and exit status" to assert only that
the terminal pane and remote status payload enter `reconnecting` and that the
detail includes SSH retry attempt/context (e.g., attempt number and retry
reason) but does not assert or display raw SSH exit codes; update any references
to "exit status" in the same step to instead refer to "retry context" or "retry
reason" to match the reconnect copy behavior.

In `@Resources/Localizable.xcstrings`:
- Around line 111979-112029: The three new localization keys
remote.state.reconnecting.terminal, remote.state.disconnected.terminalEnded, and
remote.state.targetFallback only include en and ja; add entries for all
supported locales (ar, bs, da, de, en, es, fr, it, ja, ko, nb, pl, pt-BR, ru,
th, tr, uk, zh-Hans, zh-Hant) under each key’s localizations block; for
lower-confidence locales (ar, bs, da, pl, th, tr) use English fallback values
but set stringUnit.state to "translated", and for the remaining locales provide
proper translations or English fallbacks as required so every locale present in
the catalog is included for each of the three keys.

In `@Sources/Workspace.swift`:
- Around line 9732-9750: The reconnect handler
markRemoteTerminalSessionReconnecting currently only validates
surfaceId/relayPort via remoteTerminalLifecycleMatches and can apply stale
updates; update it to reject stale events by comparing a stored lifecycle
generation or last-applied sequence/timestamp before applying .reconnecting.
Concretely: extend remoteTerminalLifecycleMatches (or add a new check) to accept
the incoming event's generation/sequence/timestamp (which the retry logic should
supply) and compare it against the lifecycle's current
generation/lastAppliedPhase stored on the Workspace; only call
applyRemoteConnectionStateUpdate(.reconnecting, ...) when the incoming
generation is strictly newer than the stored one (mirror the same freshness
check used by markRemoteTerminalSessionConnected), so out-of-order/delayed
reconnect callbacks cannot regress the state.

---

Outside diff comments:
In `@Sources/Workspace.swift`:
- Around line 9727-9728: The pendingRemoteTerminalChildExitSurfaceIds set is
left containing surfaceId entries that are treated as live by
remoteTerminalLifecycleMatches, allowing delayed lifecycle callbacks to mutate
state after a terminal pane is gone; when you call
untrackRemoteTerminalSurface(surfaceId) (or during the panel/disconnect cleanup
path) also remove that surfaceId from pendingRemoteTerminalChildExitSurfaceIds
so it no longer matches, or alternatively stop consulting
pendingRemoteTerminalChildExitSurfaceIds inside remoteTerminalLifecycleMatches —
update the cleanup code that runs when a panel disconnects/actually closes (the
same area that calls untrackRemoteTerminalSurface and the panel/disconnect
cleanup around the 9780–9788 changes) to clear
pendingRemoteTerminalChildExitSurfaceIds.remove(surfaceId).
🪄 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: cba5c117-8b69-4faa-9a2c-8b965091fd34

📥 Commits

Reviewing files that changed from the base of the PR and between 7cb94ba and 4f9bad6.

📒 Files selected for processing (8)
  • CLI/cmux.swift
  • Resources/Localizable.xcstrings
  • Sources/TerminalController.swift
  • Sources/Workspace.swift
  • cmuxTests/SSHStartupSignalLifecycleTests.swift
  • cmuxTests/WorkspaceRemoteConnectionTests.swift
  • docs/cli-contract.md
  • docs/remote-daemon-spec.md

Comment thread docs/remote-daemon-spec.md Outdated
Comment thread Resources/Localizable.xcstrings Outdated
Comment thread Sources/Workspace.swift Outdated

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit a3026dd. Configure here.

Comment thread Sources/Workspace.swift Outdated

@cubic-dev-ai cubic-dev-ai 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.

1 issue found across 2 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread Sources/Workspace.swift Outdated
Comment thread Resources/Localizable.xcstrings Outdated
@austinywang
austinywang dismissed coderabbitai[bot]’s stale review May 19, 2026 05:28

Stale CodeRabbit changes-requested review for 4f9bad6. All inline findings have been addressed/resolved, and the latest CodeRabbit status check is passing on the current head.

@cubic-dev-ai cubic-dev-ai 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.

7 issues found across 8 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="Resources/Localizable.xcstrings">

<violation number="1" location="Resources/Localizable.xcstrings:112021">
P2: French (fr) translation uses 'a' (has) instead of 'à' (to/at). 'Reconnexion a' would be read as 'Reconnection has' rather than 'Reconnection to'. The grave accent on 'à' is required for correctness.</violation>

<violation number="2" location="Resources/Localizable.xcstrings:112045">
P2: Norwegian Bokmål (nb) translation uses 'pa' and 'forsok' instead of the correct 'på' and 'forsøk'. The Norwegian letters 'å' and 'ø' must be used for the text to be correct and readable.</violation>

<violation number="3" location="Resources/Localizable.xcstrings:112134">
P2: Spanish (es) translation is missing required accents: 'sesion' should be 'sesión' and 'termino' should be 'terminó'. The missing accents change the meaning - 'termino' reads as 'term' (noun) instead of 'ended' (verb).</violation>

<violation number="4" location="Resources/Localizable.xcstrings:112140">
P2: French (fr) translation uses 'terminee' instead of 'terminée'. Required accents on the final 'e' are missing.</violation>

<violation number="5" location="Resources/Localizable.xcstrings:112164">
P2: Norwegian Bokmål (nb) translation uses 'okten' without the required 'ø'. Should be 'økten' (the session).</violation>

<violation number="6" location="Resources/Localizable.xcstrings:112176">
P2: Portuguese (pt-BR) translation uses 'sessao' instead of 'sessão'. The required tilde on 'a' is missing.</violation>

<violation number="7" location="Resources/Localizable.xcstrings:112259">
P2: French (fr) translation uses 'hote' instead of 'hôte'. The required circumflex on 'ô' is missing.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread Resources/Localizable.xcstrings Outdated
"pt-BR": {
"stringUnit": {
"state": "translated",
"value": "A sessao SSH com %@ terminou."

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Portuguese (pt-BR) translation uses 'sessao' instead of 'sessão'. The required tilde on 'a' is missing.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Resources/Localizable.xcstrings, line 112176:

<comment>Portuguese (pt-BR) translation uses 'sessao' instead of 'sessão'. The required tilde on 'a' is missing.</comment>

<file context>
@@ -111979,51 +111979,357 @@
+        "pt-BR": {
+          "stringUnit": {
+            "state": "translated",
+            "value": "A sessao SSH com %@ terminou."
+          }
+        },
</file context>
Suggested change
"value": "A sessao SSH com %@ terminou."
"value": "A sessão SSH com %@ terminou."

Comment thread Resources/Localizable.xcstrings Outdated
"fr": {
"stringUnit": {
"state": "translated",
"value": "hote distant"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: French (fr) translation uses 'hote' instead of 'hôte'. The required circumflex on 'ô' is missing.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Resources/Localizable.xcstrings, line 112259:

<comment>French (fr) translation uses 'hote' instead of 'hôte'. The required circumflex on 'ô' is missing.</comment>

<file context>
@@ -111979,51 +111979,357 @@
+        "fr": {
+          "stringUnit": {
+            "state": "translated",
+            "value": "hote distant"
+          }
+        },
</file context>
Suggested change
"value": "hote distant"
"value": "hôte distant"

Comment thread Resources/Localizable.xcstrings Outdated
"fr": {
"stringUnit": {
"state": "translated",
"value": "La session SSH vers %@ est terminee."

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: French (fr) translation uses 'terminee' instead of 'terminée'. Required accents on the final 'e' are missing.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Resources/Localizable.xcstrings, line 112140:

<comment>French (fr) translation uses 'terminee' instead of 'terminée'. Required accents on the final 'e' are missing.</comment>

<file context>
@@ -111979,51 +111979,357 @@
+        "fr": {
+          "stringUnit": {
+            "state": "translated",
+            "value": "La session SSH vers %@ est terminee."
+          }
+        },
</file context>
Suggested change
"value": "La session SSH vers %@ est terminee."
"value": "La session SSH vers %@ est terminée."

Comment thread Resources/Localizable.xcstrings Outdated
"nb": {
"stringUnit": {
"state": "translated",
"value": "SSH-okten til %@ er avsluttet."

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Norwegian Bokmål (nb) translation uses 'okten' without the required 'ø'. Should be 'økten' (the session).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Resources/Localizable.xcstrings, line 112164:

<comment>Norwegian Bokmål (nb) translation uses 'okten' without the required 'ø'. Should be 'økten' (the session).</comment>

<file context>
@@ -111979,51 +111979,357 @@
+        "nb": {
+          "stringUnit": {
+            "state": "translated",
+            "value": "SSH-okten til %@ er avsluttet."
+          }
+        },
</file context>
Suggested change
"value": "SSH-okten til %@ er avsluttet."
"value": "SSH-økten til %@ er avsluttet."

Comment thread Resources/Localizable.xcstrings Outdated
"nb": {
"stringUnit": {
"state": "translated",
"value": "Kobler til %@ pa nytt (forsok %lld/%lld)"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Norwegian Bokmål (nb) translation uses 'pa' and 'forsok' instead of the correct 'på' and 'forsøk'. The Norwegian letters 'å' and 'ø' must be used for the text to be correct and readable.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Resources/Localizable.xcstrings, line 112045:

<comment>Norwegian Bokmål (nb) translation uses 'pa' and 'forsok' instead of the correct 'på' and 'forsøk'. The Norwegian letters 'å' and 'ø' must be used for the text to be correct and readable.</comment>

<file context>
@@ -111979,51 +111979,357 @@
+        "nb": {
+          "stringUnit": {
+            "state": "translated",
+            "value": "Kobler til %@ pa nytt (forsok %lld/%lld)"
+          }
+        },
</file context>
Suggested change
"value": "Kobler til %@ pa nytt (forsok %lld/%lld)"
"value": "Kobler til %@ på nytt (forsøk %lld/%lld)"

Comment thread Resources/Localizable.xcstrings Outdated
"es": {
"stringUnit": {
"state": "translated",
"value": "La sesion SSH con %@ termino."

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Spanish (es) translation is missing required accents: 'sesion' should be 'sesión' and 'termino' should be 'terminó'. The missing accents change the meaning - 'termino' reads as 'term' (noun) instead of 'ended' (verb).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Resources/Localizable.xcstrings, line 112134:

<comment>Spanish (es) translation is missing required accents: 'sesion' should be 'sesión' and 'termino' should be 'terminó'. The missing accents change the meaning - 'termino' reads as 'term' (noun) instead of 'ended' (verb).</comment>

<file context>
@@ -111979,51 +111979,357 @@
+        "es": {
+          "stringUnit": {
+            "state": "translated",
+            "value": "La sesion SSH con %@ termino."
+          }
+        },
</file context>
Suggested change
"value": "La sesion SSH con %@ termino."
"value": "La sesión SSH con %@ terminó."

Comment thread Resources/Localizable.xcstrings Outdated
"fr": {
"stringUnit": {
"state": "translated",
"value": "Reconnexion a %@ (tentative %lld/%lld)"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: French (fr) translation uses 'a' (has) instead of 'à' (to/at). 'Reconnexion a' would be read as 'Reconnection has' rather than 'Reconnection to'. The grave accent on 'à' is required for correctness.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Resources/Localizable.xcstrings, line 112021:

<comment>French (fr) translation uses 'a' (has) instead of 'à' (to/at). 'Reconnexion a' would be read as 'Reconnection has' rather than 'Reconnection to'. The grave accent on 'à' is required for correctness.</comment>

<file context>
@@ -111979,51 +111979,357 @@
+        "fr": {
+          "stringUnit": {
+            "state": "translated",
+            "value": "Reconnexion a %@ (tentative %lld/%lld)"
+          }
+        },
</file context>
Suggested change
"value": "Reconnexion a %@ (tentative %lld/%lld)"
"value": "Reconnexion à %@ (tentative %lld/%lld)"

Resolve the obsolete SSH-wrapper status patch against the current native Cloud session owner. CloudTuiManualMirrorSession already publishes reconnecting attempt/reason, attached and ended states and fences stale attachments; its presentation policy intentionally keeps transient recovery silent and offers Reconnect after persistent failure. Current SSH terminal lifecycle and attempt IDs also replace the old per-surface sequence helpers. Preserve main's behavior and existing recovery tests rather than reinstall duplicate status RPCs.
@cursor

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

Copy link
Copy Markdown
Contributor

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

@teamleaderleo

Copy link
Copy Markdown
Collaborator

cmux-reconcile: close-candidate

Proposed action: Close this empty PR without merging; preserve the branch.

Evidence checked September 18, 2026: GitHub reports 0 changed files, 0 additions, and 0 deletions. I independently fetched the PR diff and it is empty. Head: 983f3384fa1c8d25903fd38e1cc0a42201d61325. Target branch: main.

There is no remaining patch in this PR against its target branch. This does not establish that the original feature shipped to main or that its idea should be discarded. If this is an intentional placeholder for future work, keep the underlying request and record that purpose; otherwise it is ready for queue cleanup.

Recheck the head/diff before acting in case new work arrives. Search "cmux-reconcile: close-candidate" in this repository for this cleanup batch.

@lawrencecchen

Copy link
Copy Markdown
Contributor

Fleet instruction update for head 983f3384fa1c8d25903fd38e1cc0a42201d61325: this PR is classified other. No macOS build tag is claimed. The current controller app recipe does not establish iOS/test readiness; that requires the appropriate validated recipe. Use cmux-ci for supported jobs, retain the returned ID and receipt, and wait on the same ID after any timeout. Do not use retired maclease allocation or post credentials. Exact-head tags will be posted only after the applicable build succeeds.

@teamleaderleo

Copy link
Copy Markdown
Collaborator

Closing as already on main: merging this branch into main at 8421357 produces main's own tree, so there's nothing left to land. The branch is kept; reopen if something here is still missing. Part of the backlog cleanup in manaflow-ai/cmuxterm-hq#563.

@github-project-automation github-project-automation Bot moved this from Todo to Done in cmux backlog Sep 24, 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.

Make Cloud VM SSH sessions resilient to sleep and reconnects

3 participants