Skip to content

Add persistent Freestyle sshd cloud slot - #6409

Merged
lawrencecchen merged 117 commits into
mainfrom
task-pinned-sshd-freestyle-cloud
Jul 7, 2026
Merged

lawrencecchen merged 117 commits into
mainfrom
task-pinned-sshd-freestyle-cloud

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Jun 18, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • make default cmux vm new use one persistent Freestyle VM slot via a stable idempotency key
  • open the default cloud slot through SSH into a pinned sshd workspace and reuse that workspace when it already exists
  • resume paused VMs before minting attach or SSH credentials

Verification

  • bunx tsc --noEmit --pretty false
  • bun test tests/vm-workflows.test.ts (DB-backed cases skipped without CMUX_DB_TEST=1)
  • xcodebuild test -quiet -project cmux.xcodeproj -scheme cmux-unit -configuration Debug -destination 'platform=macOS' -derivedDataPath /tmp/cmux-cloudsshd -only-testing:cmuxTests/CLINotifyProcessIntegrationRegressionTests/testVMNewDefaultCreatesPinnedSSHDWorkspaceOverFreestyleSSH -only-testing:cmuxTests/CLINotifyProcessIntegrationRegressionTests/testVMNewDefaultReusesPinnedSSHDWorkspaceOverFreestyleSSH -only-testing:cmuxTests/CLINotifyProcessIntegrationRegressionTests/testVMSSHOpensManagedWorkspaceThroughSharedSSHPath

View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Note

High Risk
Large changes to cloud VM provisioning, credential handling (SSH passwords/askpass), workspace persistence/reuse, and remote attach/reconnect paths—mistakes can strand workspaces, leak creds in logs, or attach to the wrong VM.

Overview
Introduces a persistent default Cloud VM (cmux-default-freestyle-sshd-v1): default cmux vm new (no image/provider override) forces Freestyle, uses a fixed idempotency key, opens a pinned sshd workspace, and reuses an existing pinned workspace that matches the managed VM id and daemon slot instead of always creating a new one.

Base workflow: new cmux vm base open / base reset (socket vm.base_open / vm.base_reset), plus CLI additions such as status, snapshot/checkpoint, fork, restore, and inspect helpers (tools, ports, handoff, promote-template). Daytona is added to the CLI provider allowlist.

Attach path: managed opens prefer daemon WebSocket PTY (vm.attach_info with require_daemon); explicit cmux vm ssh still uses the SSH path (forceSSH). Freestyle SSH moves from token-in-username to short-lived password auth with askpass/expect helpers; SSH startup/reconnect logic is extracted to CMUXCLI+SSHStartupScripts.swift (password cleanup, reconnect limits, vm-pty-attach loops). ssh-info no longer prints raw passwords.

Remote model: WorkspaceRemoteConfiguration and session snapshots gain managedCloudVMID; WebSocket cloud workspaces can persist for restore; proxy broker identity treats managed cloud PTY as shared across owner workspaces; UI display can show “cloud VM” instead of the gateway hostname. Control socket surface create/split accepts remote_context. Sidebar drop planning rejects insertions outside a legal range (pins).

CI file-length budgets are bumped for touched large Swift files.

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


Summary by cubic

Pins the default Cloud VM to a persistent, account‑scoped Base opened through a reusable sshd workspace with durable attach/reconnect. Adds Base/session APIs and docs, a titlebar Cloud split button and palette actions, a daemon WebSocket CLI bridge, a reconnect overlay, and a WS‑only daytona provider.

  • New Features

    • Persistent default slot: forces Freestyle, pins Base to a stable idempotency key, reuses a pinned sshd workspace; persists managedCloudVMID and restores managed WebSocket cloud workspaces.
    • APIs/docs: Base open/reset; VM status, sessions (list/open), snapshot, fork, restore; docs at /docs/base.
    • UI: titlebar Cloud split button; Command Palette Cloud actions; menu order via ui.newWorkspace.menuSectionOrder; compact titlebar style by default; transient Cloud VM loading panel; per‑surface default cloud tmux sessions; remote_context for local/cloud splits; cloud‑terminal reconnect overlay.
    • Attach/SSH: prefer daemon WebSocket PTY; Freestyle fallback uses short‑lived password creds with new startup scripts (askpass/retry/login‑shell recovery and pasteable command); preserved scrollback.
    • Cloud CLI bridge: /tmp/cmux-cloud-cli.sock, forwards cli.request (max 4 in flight); rewrites notification.create_for_caller and scopes requests to the owning workspace/surface.
    • Provider: adds daytona (WebSocket‑only attach over preview URLs; stop/start map to pause/resume); provider enum/CLI allowlist updated.
  • Bug Fixes

    • Reconnect: republishes “connected” when the proxy endpoint is unchanged; reduces reconnect notification spam; preserves attach transport and scrollback; recovers dead default cloud terminals; ignores stale prompts.
    • SSH resilience: bounds/ignores stale prompts; prevents Freestyle password prompts; keeps SSH relay attached after auth; clearer retry status.
    • API/validation: explicit account scoping on VM routes; stricter Base selection/reset; validate session/attachment IDs; clearer errors with display titles and Retry‑After; mint daemon WebSocket leases for SSH endpoints; tighter Cloud VM identity with split tunnel/PTY identity.
    • UI/build: legal sidebar drop ranges; exclude pinned tabs from groups; fix Cloud VM loading/surface binding and restore lifecycle; refresh pinned Cloud workspaces on reconnect; only use socket‑forward transport when no daemon WebSocket endpoint; keep personal Cloud VMs visible after account migration.

Written for commit 22c389e. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

Release Notes

  • New Features
    • Opt-in “persistent default” SSH daemon behavior for Cloud VM create/open, including persistent idempotency, workspace pin/reuse, and reconnection.
    • Paused Cloud VMs now resume automatically when opened via SSH/attach.
    • Added a Cloud VM button to the application titlebar.
  • Bug Fixes
    • Improved idempotency handling for failed/destroyed VM records.
    • Preserved SSH endpoint transport details and improved ssh-info redaction.
  • Localization
    • Added Cloud VM UI strings (multiple languages).
  • Tests
    • Expanded persistent SSH, resume, and ssh-info redaction coverage.

@vercel

vercel Bot commented Jun 18, 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 Jul 7, 2026 12:16pm
cmux-staging Building Building Preview, Comment Jul 7, 2026 12:16pm

@coderabbitai

coderabbitai Bot commented Jun 18, 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

Adds SSH startup script builders with passwordCredential and pinWorkspaceToTop support, threading credentials through reusable and one-shot wrapper scripts with askpass and PTY reconnect handling. Introduces persistent freestyle sshd detection that forces provider to "freestyle", selects a dedicated idempotency key, reuses or creates a pinned "sshd" workspace, and issues workspace.remote.reconnect on reuse. On the server side, a new resume gateway operation and ensureUserVmRunning workflow wrapper auto-resume paused VMs before endpoint creation; beginCreate now retries idempotency keys after failed/destroyed rows. Explicitly defines VMSSHEndpoint with transport from server response. A TitlebarCloudVMButton is added to the macOS titlebar.

Changes

CLI: SSH startup scripts, persistent sshd, workspace pinning, and password-based auth

Layer / File(s) Summary
SSH startup script builders with passwordCredential support
CLI/CMUXCLI+SSHStartupScripts.swift
Adds buildSSHStartupCommand, buildReusableSSHStartupCommand, buildSSHPTYAttachScriptBody, sshAskpassExecShellScript, buildSSHStartupScriptBody, writeSSHStartupScript, reusableShellStartupCommand, and buildSSHSessionEndShellCommand—covering one-shot, reusable, PTY-attach, and askpass flows with session lifecycle and reconnect handling.
Persistent freestyle sshd detection and shell-entry defaults
CLI/cmux.swift
Adds usesPersistentDefaultFreestyleCloud predicate, persistent idempotency-key constants, and persistent "sshd" workspace name; forces provider to "freestyle" on match; enables forceSSH and shouldPinWorkspaceToTop for persistent path and explicitly disables them for non-persistent open-shell paths.
SSH option model extensions and passwordCredential threading
CLI/cmux.swift
Extends SSHCommandOptions with passwordCredential and pinWorkspaceToTop fields; threads credential through bootstrap/startup command builders; selects persistent daemon based on bootstrap/pinning flags; refines split-attach command construction to use argv-like arrays.
SSH gateway option construction with password tokens and vm.ssh_info request switch
CLI/cmux.swift
Sets passwordCredential: token and pinWorkspaceToTop in gateway SSHCommandOptions; switches cloud ssh-info RPC from vm.attach_info to vm.ssh_info; changes password display to redacted placeholder; adds conditional askpass wrapper execution for vm ssh-attach.
Persistent PTY workspace reuse, pin-to-top, and respawn behavior
CLI/cmux.swift
Refactors persistent PTY workspace setup to scan for reusable named workspace with pinned preference, tracks didCreateWorkspace, conditionally renames/pins on create, issues workspace.remote.reconnect on reuse, gates rollback workspace.close only when workspace was created, applies shouldPinWorkspaceToTop flag in remote configuration.
Baked daemon preflight capability exclusion for persistent PTY
Packages/macOS/CmuxRemoteSession/Sources/.../RemoteSessionCoordinator.swift, Packages/macOS/CmuxRemoteSession/Tests/.../RemotePortScanGatingTests.swift
Removes requiredPTYPersistentDaemonCapability from baked-daemon preflight checks; extends makeCoordinator test harness to accept preserveAfterTerminalExit, persistentDaemonSlot, skipDaemonBootstrap; adds bakedVMPreflightIgnoresPersistentPTYCapabilities test.
Titlebar Cloud VM button UI component
Sources/Update/TitlebarCloudVMButton.swift, Sources/Update/UpdateTitlebarAccessory.swift, Resources/Localizable.xcstrings
Adds TitlebarCloudVMButton SwiftUI view invoking performCloudVMAction, inserts it into TitlebarControlsView, and adds localized strings for label, tooltip, and cli.vm.create.createdCloudVM.
VMSSHEndpoint model definition and transport field
Sources/Cloud/VMClient.swift
Explicitly defines VMSSHEndpoint struct with nested Credential enum and transport/host/port/username/credential/publicKeyFingerprint fields; updates decodeSSHEndpoint to read transport from server response with trimming/lowercasing and "ssh" default.
CLI build wiring, SSH info payload transport field, and test fixture updates
cmux.xcodeproj/project.pbxproj, Sources/Cloud/VMClientSocketCommands.swift, cmuxTests/VMSSHCommandTests.swift
Updates Xcode project build phases to compile new Swift sources; adds transport field to socketWorkerSSHInfoPayload and switches SSH attach payload to use it directly; updates existing SSH command tests to expect vm.ssh_info and validate decoded startup command assertions including password redaction.
New CLI integration tests for default sshd workspace behavior
cmuxTests/VMDefaultCloudCommandTests.swift
Adds testVMNewDefaultCreatesPinnedSSHDWorkspaceOverFreestyleSSH (validates full create/rename/action/configure/select flow) and testVMNewDefaultReusesPinnedSSHDWorkspaceOverFreestyleSSH (validates reuse with surface.list and workspace.remote.reconnect); includes base64-decode helpers for nested startup command assertions.

Web: VM resume-on-access and idempotency retry after terminal failure

Layer / File(s) Summary
Provider gateway resume contract
web/services/vms/providerGateway.ts
Extends VmProviderGatewayShape with optional resume(provider, vmId) operation and wires live implementation to call provider's resume(vmId) via providerEffect with "resume" operation label.
Create idempotency-key handling for failed/destroyed VM rows
web/services/vms/repository.ts
Updates beginCreate idempotency checks (before and after advisory lock) to return existing VM only for non-terminal statuses; for failed/destroyed rows, clears idempotencyKey and refreshes updatedAt to allow creation to continue.
Resume paused VMs before opening attach and SSH endpoints
web/services/vms/workflows.ts
Wraps requireUserVm with ensureUserVmRunning in openAttachEndpoint and openSshEndpoint; adds ensureUserVmRunning to conditionally call providers.resume, update provider-observed status to running, emit vm.resumed usage event with source metadata, and return updated or original VM.
Web workflow tests for idempotency retry and resume ordering
web/tests/vm-workflows.test.ts, web/tests/notifications-push-route.test.ts
Adds DB-backed tests for re-create after failed idempotency key and paused-VM SSH resume ordering; adds closeCloudDbForTests stub to notifications push route mock.

Sequence Diagrams

sequenceDiagram
  rect rgba(100, 149, 237, 0.5)
    Note over vmNew,workspaceOps: Persistent Freestyle sshd — vm new (create path)
  end
  participant vmNew as vm new (CLI)
  participant mockRPC as JSON-RPC Server
  participant workspaceOps as workspace RPC ops

  vmNew->>mockRPC: vm.create (provider=freestyle, idempotency_key)
  mockRPC-->>vmNew: vmId
  vmNew->>mockRPC: vm.ssh_info (id=vmId)
  mockRPC-->>vmNew: SSH endpoint + password credential
  vmNew->>mockRPC: workspace.list
  mockRPC-->>vmNew: [] (empty)
  vmNew->>workspaceOps: workspace.create (initial_command script path)
  workspaceOps-->>vmNew: workspaceId
  vmNew->>workspaceOps: workspace.rename (title=sshd)
  vmNew->>workspaceOps: workspace.action pin
  vmNew->>workspaceOps: workspace.action move_top
  vmNew->>workspaceOps: workspace.remote.configure (terminal_startup_command)
  vmNew->>mockRPC: workspace.select
  mockRPC-->>vmNew: OK workspace=... state=connecting
Loading
sequenceDiagram
  rect rgba(100, 200, 150, 0.5)
    Note over openSshEndpoint,usage: Server-side paused VM resume before SSH endpoint
  end
  participant openSshEndpoint
  participant ensureUserVmRunning
  participant providers as VmProviderGateway
  participant repo as VmRepository
  participant usage as UsageEvents

  openSshEndpoint->>ensureUserVmRunning: requireUserVm result (status=paused)
  ensureUserVmRunning->>providers: resume(provider, providerVmId)
  providers-->>ensureUserVmRunning: VMHandle
  ensureUserVmRunning->>repo: markProviderObservedStatus(running)
  ensureUserVmRunning->>usage: emit vm.resumed (source=ssh)
  ensureUserVmRunning-->>openSshEndpoint: VM (status=running)
  openSshEndpoint-->>openSshEndpoint: mint SSH credential and return endpoint
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • manaflow-ai/cmux#5301: Both PRs extend SSHCommandOptions with additional session context and thread it through SSH startup script generation (main PR adds passwordCredential/pinWorkspaceToTop + ssh-info transport; retrieved PR adds agentSocketPath forwarding).

Poem

🐇 A bunny hops to the cloud with glee,
Pinning workspaces high for all to see!
Passwords askpass'd, PTY sessions reborn,
Paused VMs wake at SSH's horn.
With freestyle sshd neatly aligned,
No lease-token leaks shall be left behind! 🌤️


Important

Pre-merge checks failed

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

❌ Failed checks (3 errors, 1 warning)

Check name Status Explanation Resolution
Cmux Swift Blocking Runtime ❌ Error CLI/cmux.swift line 9116 introduces usleep(300000) in new recoverStaleFreestyleSSHPromptIfNeeded function to wait for shell readiness—hardcoded timing without real signal violates swift-blocking-ru... Replace the 300ms usleep with a real readiness signal: poll for prompt output in the respawned shell, retry send_text with backoff, or wait on terminal readiness callback.
Cmux Full Internationalization ❌ Error Two new localization keys (titlebar.cloudVM.accessibilityLabel, titlebar.cloudVM.tooltip) in Resources/Localizable.xcstrings have translations only for en, ja but the catalog supports 20 locales in... Add translations for all 20 catalog locales to titlebar.cloudVM.accessibilityLabel and titlebar.cloudVM.tooltip in Resources/Localizable.xcstrings, matching the pattern used for cli.vm.create.createdCloudVM which has complete translations.
Cmux Architecture Rethink ❌ Error PR introduces hardcoded usleep(300000) in new recoverStaleFreestyleSSHPromptIfNeeded() to paper over shell-readiness race after respawn, violating swift-architectural-rethink.md's prohibition on ti... Replace usleep with proper shell readiness detection: poll for prompt output, use completion callback from respawn, or retry send with exponential backoff on failure.
Docstring Coverage ⚠️ Warning Docstring coverage is 4.44% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (18 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 PR introduces no Swift 6 actor isolation violations: VMSSHEndpoint is an implicitly-Sendable value struct, nonisolated static functions correctly handle values, TitlebarCloudVMButton is a SwiftUI V...
Cmux Expensive Synchronous Load ✅ Passed PR adds no expensive synchronous loaders (RestorableAgentSessionIndex.load(), per-record syscalls, directory scans, broad JSON decode) to main-actor or interactive paths per swift-expensive-sync-lo...
Cmux Cache Substitution Correctness ✅ Passed PR makes fresh reads of workspace/VM state from workspace.list and database before use, with subsequent RPC/DB calls providing validation. No substitution of fresh authoritative reads with unvalida...
Cmux No Hacky Sleeps ✅ Passed No production TypeScript/JavaScript code introduces hacky sleeps. The only sleep found is a deterministic test-only helper in vm-workflows.test.ts that polls database locks, which is allowed by the...
Cmux Algorithmic Complexity ✅ Passed PR does not violate algorithmic complexity rules. reusableNamedWorkspace uses single-pass linear iteration with early termination; recoverStaleFreestyleSSHPromptIfNeeded operates on fixed-size coll...
Cmux Swift Concurrency ✅ Passed The PR introduces no new legacy async patterns. All DispatchQueue usage in cmux.swift is in pre-existing infrastructure (websocket bridge, socket initialization, file watching); new SSH functions (...
Cmux Swift @Concurrent ✅ Passed All new/changed Swift functions comply with @concurrent rules: SSH startup builders are synchronous, UI button delegates to non-async launcher, no nonisolated async without @concurrent, no @concurr...
Cmux Swift File And Package Boundaries ✅ Passed PR introduces 292-line CMUXCLI+SSHStartupScripts.swift (under 400-line threshold with clear single responsibility), 26-line TitlebarCloudVMButton.swift UI component, and maintains CLI/cmux.swift at...
Cmux Swiftpm Lockfiles ✅ Passed All SwiftPM lockfiles are properly committed: 11 package-local Package.resolved files paired with Package.swift dependency changes, root Xcode Package.resolved included, and no .gitignore files ign...
Cmux Swift Logging ✅ Passed All logging in the PR complies with swift-logging.md: CLI output via print() is allowed, cmuxDebugLog() is debug-only (#if DEBUG guarded), printf in generated shell scripts are not Swift logging, a...
Cmux User-Facing Error Privacy ✅ Passed All user-facing errors, alerts, and output in the PR comply with privacy rules: passwords are redacted in ssh-info output, no credentials/tokens/database details are exposed, error messages are gen...
Cmux Swiftui State Layout ✅ Passed TitlebarCloudVMButton is a value-based struct View with no state management; UpdateTitlebarAccessory only touches legacy state incidentally per the allowed cases.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed TitlebarCloudVMButton is a simple SwiftUI View button embedded in existing titlebar HStack, not a standalone window/panel/WindowGroup; falls under allowed "views that are not standalone key windows...
Cmux Source Artifacts ✅ Passed All 17 changed files are intentional source files, tests, project configs, or localization catalogs. No local tool output, generated code, artifacts, caches, DerivedData, or scratch directories det...
Title check ✅ Passed The title is concise and accurately captures the main change: a persistent Freestyle SSHD cloud slot.
Description check ✅ Passed It includes the required Summary and Verification sections with concrete details and testing results.
✨ 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 task-pinned-sshd-freestyle-cloud

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 Jun 19, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces a persistent default Freestyle VM slot (cmux-default-freestyle-sshd-v1) with a pinned sshd workspace that is reused across sessions, moving cloud VM attach from raw SSH credentials to short-lived passwords with SSH_ASKPASS/expect and daemon WebSocket PTY leases. It also adds VM lifecycle APIs (status, snapshot, restore, fork, sessions), a cloud CLI bridge for forwarding cli.request events from the VM back to the Mac app, and a unified Cloud VM titlebar button.

  • Persistent slot & reuse: cmux vm new with no image now resolves or mints one stable cmux-default-freestyle-sshd-v1 slot; the sshd workspace is matched by title and managed_cloud_vm_id on subsequent calls and reused instead of creating a new one each time.
  • Attach & SSH overhaul: Paused VMs are auto-resumed via ensureUserVmRunning before minting attach or SSH credentials; short-lived passwords are injected via SSH_ASKPASS/expect scripts extracted to CMUXCLI+SSHStartupScripts.swift; daemon WebSocket leases are stored in the DB and forwarded to the client.
  • Cloud CLI bridge & VM sessions: A new Go binary (cmuxd-remote) opens a Unix socket for the CLI to forward cli.request RPCs to the connected Mac app; the cloudVmSessions table tracks per-session attach counts and PTY dimensions.

Confidence Score: 3/5

Merge blocked by multiple unresolved issues: a concurrent double-resume at the provider can corrupt VM status to paused while the VM is actually running; a world-writable bridge socket lets any VM process inject CLI commands to the Mac app; non-atomic idempotency-key clearing can produce two billing-active default-slot VMs; auto-resume bypasses the caller's current entitlement ceiling.

The PR introduces auto-resume through reservePausedResume, but the implementation returns the already-running row when a concurrent caller holds the advisory lock, causing the second caller to invoke resume() on a live VM and then corrupt the DB status to paused on provider rejection. The cloud CLI bridge socket uses 0o666, allowing any process on the VM to reach the Mac app. The non-Freestyle fork path creates a provider snapshot before reserving credits, leaving an orphaned snapshot when createVm fails. The stale-plan-limit default in ensureUserVmRunning allows auto-resume to bypass a user's current entitlement.

web/services/vms/repository.ts (reservePausedResume race), web/services/vms/workflows.ts (ensureUserVmRunning stale limit and non-Freestyle fork ordering), daemon/remote/cmd/cmuxd-remote/cloud_cli_bridge.go (socket permissions), web/app/api/vm/restore/route.ts and fork/route.ts (JSON parse and providerField error handling)

Important Files Changed

Filename Overview
web/services/vms/workflows.ts Core workflow orchestration: adds ensureUserVmRunning (auto-resume), snapshotVm, restoreVm, forkVm, openVmSession; multiple unresolved issues including stale plan limit on auto-resume, double-resume race, and orphaned provider snapshot on non-freestyle fork failure.
web/services/vms/repository.ts Adds reservePausedResume, hasOwnedSnapshot, upsertVmSession; the reservePausedResume implementation returns an already-running row on concurrent calls, causing double-resume at the provider and potential DB status corruption. Non-atomic idempotency-key clearing also present.
daemon/remote/cmd/cmuxd-remote/cloud_cli_bridge.go New cloud CLI Unix socket bridge forwarding CLI requests to the connected Mac app; socket created with 0o666 (world-writable), allowing any process on the VM to inject arbitrary CLI requests to the Mac app without holding the daemon WebSocket lease token.
CLI/cmux.swift +1482 lines: adds persistent-slot VM new, attach-info/ssh-info retry loops; both loops use usleep() for per-iteration delay (blocking thread for up to 240 s total), violating the Swift blocking runtime rule. File also exceeds its bumped budget by ~52 lines.
CLI/CMUXCLI+SSHStartupScripts.swift New 333-line file extracting SSH startup script builders; usleep(300000) used as timing-based readiness wait for a respawned shell instead of a real signal, violating the blocking runtime rule.
web/app/api/vm/[id]/fork/route.ts New fork route; optionalObjectBody calls JSON.parse without try/catch, returning 500 instead of 400 on malformed JSON body.
web/app/api/vm/restore/route.ts New restore route; providerField throws a raw Error outside the try/catch block (returns 500 for invalid provider values instead of 400), and requiredObjectBody uses JSON.parse without a catch for SyntaxError.
web/services/vms/drivers/freestyle.ts Adds fork(), signed admin token provisioning, and WebSocket session/attachment ID threading; logic looks correct, admin token correctly inherited from source providerMetadata for forked VMs.
web/services/vms/providerGateway.ts snapshot and restore gateway call-throughs lack the driver-capability guard that fork and getStatus use; a partially-implementing driver will throw a TypeError (unhandled 500) instead of a clean capability error.
Sources/Update/TitlebarCloudVMButton.swift New 436-line SwiftUI split-button for the cloud VM titlebar; uses String(localized:) throughout; all new localized keys are present in Localizable.xcstrings with at least en+ja translations consistent with existing command.* patterns.
web/db/migrations/20260624002730_cloud_vm_sessions/migration.sql Adds cloudVmSessions table with composite unique key on (vmId, providerSessionId); schema looks correct and consistent with the upsertVmSession conflict target.

Reviews (67): Last reviewed commit: "Improve Cloud VM loading and error state..." | Re-trigger Greptile

Comment thread web/services/vms/workflows.ts Outdated
Comment on lines +398 to +407
yield* repo.recordUsageEvent({
userId: vm.userId,
billingTeamId: vm.billingTeamId,
billingPlanId: vm.billingPlanId,
vmId: vm.id,
eventType: "vm.resumed",
provider: vm.provider,
imageId: vm.imageId,
metadata: { source: "attach" },
}).pipe(Effect.catchAll(() => Effect.void));

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 Hardcoded source: "attach" for SSH-triggered resumes

ensureUserVmRunning is called from both openAttachEndpoint and openSshEndpoint, but the usage event always records metadata: { source: "attach" }. Resume events triggered via openSshEndpoint (i.e., when vm ssh_info is called for a paused Freestyle slot) will be misclassified as attach-sourced in analytics and billing history.

Comment thread CLI/cmux.swift
Comment on lines 9005 to +9020
}
}

private func reusableNamedWorkspace(
named rawName: String?,
windowRaw: String?,
client: SocketClient
) throws -> (workspaceId: String, windowId: String?)? {
guard let name = rawName, !name.isEmpty else { return nil }
var params: [String: Any] = [:]
try applyWindowOrCallerContext(to: &params, client: client, windowRaw: windowRaw)
let payload = try client.sendV2(method: "workspace.list", params: params)
let workspaces = payload["workspaces"] as? [[String: Any]] ?? []
let matches = workspaces.filter { workspace in
(workspace["title"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines) == name
}

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 O(N) full workspace list scan on every default vm new

reusableNamedWorkspace fetches the full workspace.list response and then iterates all entries with .filter { ... } to find a matching title. This linear scan runs on every default cmux vm new invocation — before a workspace is even created — and scales with the number of open workspaces in the window. If the server-side workspace.list API accepts a title filter or a pinned_only flag, the filtering should be pushed to the server to avoid fetching all workspace payloads.

Rule Used: Flag production code that adds nested full-collect... (source)

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!

@lawrencecchen
lawrencecchen force-pushed the task-pinned-sshd-freestyle-cloud branch from 5ecf265 to d113d72 Compare June 19, 2026 00:05

@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

🤖 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 `@CLI/cmux.swift`:
- Around line 9017-9021: The workspace lookup in lines 9018-9021 performs
multiple iterations over the workspaces array by chaining filter and first
operations, which violates the single-pass lookup requirement for scalable
lists. Consolidate these operations into a single pass through the workspaces
array that directly returns the first workspace matching the title and is
pinned, or falls back to the first workspace with just a matching title, without
calling separate filter and first methods sequentially.

In `@web/services/vms/workflows.ts`:
- Around line 314-318: The resumed-usage metadata is incorrectly recording the
source as a hardcoded "attach" value regardless of what actually triggered the
resume operation. Locate all instances where the resumed-usage metadata source
is set (around line 406 and also in the ranges 353-357 and 381-407), and replace
the hardcoded source: "attach" with a dynamic value that correctly reflects the
actual source of the operation. For the openSshEndpoint path, use an appropriate
source identifier that distinguishes it from other resume triggers like attach
operations. Ensure the source metadata accurately represents which operation
initiated the resume across all affected code paths.

In `@web/tests/vm-workflows.test.ts`:
- Around line 332-375: The test currently verifies that resume and openSSH are
each called once (via resumeCalls and sshCalls counters), but does not enforce
the call ordering requirement. Modify the test to track the sequence of function
calls by introducing a call order tracking mechanism (such as an array that
records which function was called in sequence). Then add explicit assertions to
verify that the resume function is invoked before the openSSH function, ensuring
the correct execution order in the openSshEndpoint workflow.
🪄 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: 871c5088-775b-4b5a-8fb8-2898e36ca25b

📥 Commits

Reviewing files that changed from the base of the PR and between d7e71bb and 5ecf265.

📒 Files selected for processing (7)
  • CLI/cmux.swift
  • Sources/Cloud/VMClientSocketCommands.swift
  • cmuxTests/VMSSHCommandTests.swift
  • web/services/vms/providerGateway.ts
  • web/services/vms/repository.ts
  • web/services/vms/workflows.ts
  • web/tests/vm-workflows.test.ts

Comment thread CLI/cmux.swift Outdated
Comment thread web/services/vms/workflows.ts Outdated
Comment thread web/tests/vm-workflows.test.ts
@lawrencecchen
lawrencecchen force-pushed the task-pinned-sshd-freestyle-cloud branch from d113d72 to bcec8da Compare June 19, 2026 00:09
Comment thread CLI/cmux.swift Outdated
@lawrencecchen
lawrencecchen force-pushed the task-pinned-sshd-freestyle-cloud branch from bcec8da to b671a44 Compare June 19, 2026 00:16
Comment thread CLI/cmux.swift
@lawrencecchen
lawrencecchen force-pushed the task-pinned-sshd-freestyle-cloud branch from b671a44 to dd76540 Compare June 19, 2026 00:23

@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

Caution

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

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

145-148: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not clobber the endpoint transport in attach SSH payloads.

At Line 147, payload["transport"] = "ssh" overwrites the transport copied from socketWorkerSSHInfoPayload(ssh). That defeats the new transport contract and can misroute non-default SSH transports.

Suggested fix
         case .ssh(let ssh):
             var payload = socketWorkerSSHInfoPayload(ssh)
-            payload["transport"] = "ssh"
             return payload
🤖 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/Cloud/VMClientSocketCommands.swift` around lines 145 - 148, In the
SSH case handler of the code, the line `payload["transport"] = "ssh"`
unconditionally overwrites the transport value that was already set by
socketWorkerSSHInfoPayload(ssh). Remove this line entirely and let
socketWorkerSSHInfoPayload handle setting the transport appropriately, or
conditionally set the transport only if it is not already present in the
payload. This ensures that non-default SSH transports copied from
socketWorkerSSHInfoPayload are not overwritten and that the new transport
contract is properly respected.
🤖 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/VMDefaultCloudCommandTests.swift`:
- Around line 76-82: The test for the "workspace.action" case currently only
verifies that each individual action is either "pin" or "move_top", but does not
assert that both actions are actually invoked across the full test sequence.
This means a regression where "pin" is sent twice would still pass. Collect all
actions processed during the test (using a Set or Array to track them across
multiple mock responses) and add an assertion at the end of the test to verify
that the collected actions contain both "pin" and "move_top". Apply this fix to
all occurrences of the "workspace.action" case handling mentioned in the diff
(lines 76-82, 129-142, 209-215, 261-272).

In `@web/services/vms/providerGateway.ts`:
- Around line 67-68: The resume method implementation in the providerGateway
lacks defensive checks even though resume is marked as optional in the
VmProviderGatewayShape type definition. Update the resume function
implementation to match the defensive pattern used for the optional getStatus
method: add a guard that checks if the driver has a resume method before
attempting to call it, and either return a default value or throw an error
indicating the method is not supported when it's missing from the provider.

---

Outside diff comments:
In `@Sources/Cloud/VMClientSocketCommands.swift`:
- Around line 145-148: In the SSH case handler of the code, the line
`payload["transport"] = "ssh"` unconditionally overwrites the transport value
that was already set by socketWorkerSSHInfoPayload(ssh). Remove this line
entirely and let socketWorkerSSHInfoPayload handle setting the transport
appropriately, or conditionally set the transport only if it is not already
present in the payload. This ensures that non-default SSH transports copied from
socketWorkerSSHInfoPayload are not overwritten and that the new transport
contract is properly respected.
🪄 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: b2220a7f-39c8-44aa-9d37-32184eba4f03

📥 Commits

Reviewing files that changed from the base of the PR and between 5ecf265 and dd76540.

📒 Files selected for processing (10)
  • CLI/cmux.swift
  • Resources/Localizable.xcstrings
  • Sources/Cloud/VMClientSocketCommands.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/VMDefaultCloudCommandTests.swift
  • cmuxTests/VMSSHCommandTests.swift
  • web/services/vms/providerGateway.ts
  • web/services/vms/repository.ts
  • web/services/vms/workflows.ts
  • web/tests/vm-workflows.test.ts

Comment on lines +76 to +82
case "workspace.action":
let params = payload["params"] as? [String: Any] ?? [:]
XCTAssertEqual(params["workspace_id"] as? String, workspaceID)
XCTAssertEqual(params["window_id"] as? String, windowID)
let action = params["action"] as? String
XCTAssertTrue(action == "pin" || action == "move_top")
return self.v2Response(id: id, ok: true, result: ["workspace_id": workspaceID, "action": action ?? ""])

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Assert the workspace.action pair explicitly (pin + move_top).

Right now each call only checks membership in {pin, move_top}. A regression sending pin twice (or move_top twice) would still pass. Please assert the collected action list contains both required actions in each test.

Suggested test hardening
         XCTAssertEqual(
             state.commands.compactMap { self.jsonObject($0)?["method"] as? String },
             [
                 "vm.create",
                 "vm.ssh_info",
                 "workspace.list",
                 "workspace.create",
                 "workspace.rename",
                 "workspace.action",
                 "workspace.action",
                 "workspace.remote.configure",
                 "workspace.select",
             ]
         )
+        let actions = state.commands.compactMap { line -> String? in
+            guard let payload = self.jsonObject(line),
+                  payload["method"] as? String == "workspace.action",
+                  let params = payload["params"] as? [String: Any] else {
+                return nil
+            }
+            return params["action"] as? String
+        }
+        XCTAssertEqual(actions, ["pin", "move_top"])
@@
         XCTAssertEqual(
             state.commands.compactMap { self.jsonObject($0)?["method"] as? String },
             [
                 "vm.create",
                 "vm.ssh_info",
                 "workspace.list",
                 "workspace.action",
                 "workspace.action",
                 "workspace.remote.configure",
                 "workspace.select",
             ]
         )
+        let actions = state.commands.compactMap { line -> String? in
+            guard let payload = self.jsonObject(line),
+                  payload["method"] as? String == "workspace.action",
+                  let params = payload["params"] as? [String: Any] else {
+                return nil
+            }
+            return params["action"] as? String
+        }
+        XCTAssertEqual(actions, ["pin", "move_top"])

Also applies to: 129-142, 209-215, 261-272

🤖 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/VMDefaultCloudCommandTests.swift` around lines 76 - 82, The test
for the "workspace.action" case currently only verifies that each individual
action is either "pin" or "move_top", but does not assert that both actions are
actually invoked across the full test sequence. This means a regression where
"pin" is sent twice would still pass. Collect all actions processed during the
test (using a Set or Array to track them across multiple mock responses) and add
an assertion at the end of the test to verify that the collected actions contain
both "pin" and "move_top". Apply this fix to all occurrences of the
"workspace.action" case handling mentioned in the diff (lines 76-82, 129-142,
209-215, 261-272).

Comment on lines +67 to +68
resume: (provider, vmId) =>
providerEffect(provider, "resume", () => getProvider(provider).resume(vmId)),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if all VM providers implement a resume method
echo "=== Checking e2b driver for resume ==="
rg -n 'resume' web/services/vms/drivers/e2b.ts || echo "No resume found in e2b driver"

echo ""
echo "=== Checking freestyle driver for resume ==="
rg -n 'resume' web/services/vms/drivers/freestyle.ts || echo "No resume found in freestyle driver"

echo ""
echo "=== Checking driver type definitions ==="
rg -nA3 'resume' web/services/vms/drivers/types.ts

Repository: manaflow-ai/cmux

Length of output: 769


🏁 Script executed:

#!/bin/bash
# Check the actual VmProviderGatewayShape interface definition
echo "=== VmProviderGatewayShape interface ==="
sed -n '1,50p' web/services/vms/providerGateway.ts | cat -n

echo ""
echo "=== Full interface with context ==="
rg -B2 -A15 'interface VmProviderGatewayShape' web/services/vms/providerGateway.ts

Repository: manaflow-ai/cmux

Length of output: 2268


🏁 Script executed:

#!/bin/bash
# Check the VmProviderGatewayLive implementation for resume
echo "=== VmProviderGatewayLive implementation ==="
sed -n '55,75p' web/services/vms/providerGateway.ts | cat -n

echo ""
echo "=== Full resume implementation in VmProviderGatewayLive ==="
rg -B2 -A2 'resume:' web/services/vms/providerGateway.ts

Repository: manaflow-ai/cmux

Length of output: 1630


Inconsistent handling of optional resume method.

The VmProviderGatewayShape.resume is optional (marked with ? on line 21), but the live implementation at lines 67-68 calls getProvider(provider).resume(vmId) without guarding against a missing method. This mirrors getStatus on line 20, which is also optional but explicitly handles the missing case (lines 67-71) with if (!driver.getStatus) return "running".

While both current providers (e2b and freestyle) do implement resume, align the pattern by adding a guard or making resume required in the driver type to match getStatus consistency:

resume: (provider, vmId) =>
  providerEffect(provider, "resume", async () => {
    const driver = getProvider(provider);
    if (!driver.resume) throw new Error("resume not supported");
    return await driver.resume(vmId);
  }),
🤖 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 `@web/services/vms/providerGateway.ts` around lines 67 - 68, The resume method
implementation in the providerGateway lacks defensive checks even though resume
is marked as optional in the VmProviderGatewayShape type definition. Update the
resume function implementation to match the defensive pattern used for the
optional getStatus method: add a guard that checks if the driver has a resume
method before attempting to call it, and either return a default value or throw
an error indicating the method is not supported when it's missing from the
provider.

Comment on lines +67 to +68
resume: (provider, vmId) =>
providerEffect(provider, "resume", () => getProvider(provider).resume(vmId)),

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 Missing driver capability guard for resume — the if (!resume) check in ensureUserVmRunning is dead code because VmProviderGatewayLive always provides resume. When the underlying driver doesn't implement the method (e.g. E2B), getProvider(provider).resume(vmId) throws a TypeError, which providerEffect wraps as a VmProviderOperationError and surfaces as a hard failure in openSshEndpoint / openAttachEndpoint. The getStatus method shows the correct pattern: check if (!driver.getStatus) inside the gateway before calling through.

Suggested change
resume: (provider, vmId) =>
providerEffect(provider, "resume", () => getProvider(provider).resume(vmId)),
resume: (provider, vmId) =>
providerEffect(provider, "resume", async () => {
const driver = getProvider(provider);
if (!driver.resume) return undefined as unknown as VMHandle;
return await driver.resume(vmId);
}),

@lawrencecchen
lawrencecchen force-pushed the task-pinned-sshd-freestyle-cloud branch 2 times, most recently from ab40778 to 8e3a24a Compare June 19, 2026 00:54

@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

Caution

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

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

132-148: ⚠️ Potential issue | 🟡 Minor

Remove redundant transport override in socketWorkerAttachInfoPayload.

VMSSHEndpoint has a let transport: String field that is always set to "ssh" at its single instantiation point (Sources/Cloud/VMClient.swift:551). Line 147 in socketWorkerAttachInfoPayload overwrites payload["transport"] = "ssh" after copying it from socketWorkerSSHInfoPayload, which is redundant. Since endpoint.transport is immutable and always "ssh", the override accomplishes nothing and creates confusing code that appears to serve a purpose. Remove the override on line 147.

🤖 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/Cloud/VMClientSocketCommands.swift` around lines 132 - 148, In the
socketWorkerAttachInfoPayload method, the line that sets payload["transport"] =
"ssh" is redundant because the transport field from VMSSHEndpoint is already set
to "ssh" in the socketWorkerSSHInfoPayload call. Since endpoint.transport is
immutable and always "ssh", remove the redundant override assignment in the
socketWorkerAttachInfoPayload method to eliminate confusing code that appears to
serve a purpose but doesn't.
♻️ Duplicate comments (2)
cmuxTests/VMDefaultCloudCommandTests.swift (2)

214-220: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Assert the workspace.action pair explicitly (pin + move_top).

Same issue as in the first test: the handler accepts either action but doesn't verify both are invoked in the correct order.

🔒 Proposed fix to validate both actions
         )
+        let actions = state.commands.compactMap { line -> String? in
+            guard let payload = self.jsonObject(line),
+                  payload["method"] as? String == "workspace.action",
+                  let params = payload["params"] as? [String: Any] else {
+                return nil
+            }
+            return params["action"] as? String
+        }
+        XCTAssertEqual(actions, ["pin", "move_top"])
     }
 
     func decodedReusableShellStartupCommand(_ command: String) -> String {
🤖 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/VMDefaultCloudCommandTests.swift` around lines 214 - 220, The
workspace.action handler in the "workspace.action" case accepts either "pin" or
"move_top" action but does not verify that both actions are invoked in the
correct order. Instead of using a simple OR assertion with `XCTAssertTrue(action
== "pin" || action == "move_top")`, track each action invocation in a collection
or state variable and add assertions to verify that both "pin" and "move_top"
are called sequentially in the expected order. This ensures the test validates
the complete expected behavior rather than just accepting either action
independently.

76-82: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Assert the workspace.action pair explicitly (pin + move_top).

Each call only checks membership in {pin, move_top}. A regression sending pin twice (or move_top twice) would still pass. The expected method list (lines 142-143) shows two workspace.action calls, but there's no validation that one is pin and the other is move_top.

🔒 Proposed fix to validate both actions
         )
+        let actions = state.commands.compactMap { line -> String? in
+            guard let payload = self.jsonObject(line),
+                  payload["method"] as? String == "workspace.action",
+                  let params = payload["params"] as? [String: Any] else {
+                return nil
+            }
+            return params["action"] as? String
+        }
+        XCTAssertEqual(actions, ["pin", "move_top"])
     }
 
     func testVMNewDefaultReusesPinnedSSHDWorkspaceOverFreestyleSSH() throws {
🤖 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/VMDefaultCloudCommandTests.swift` around lines 76 - 82, The test
for the "workspace.action" case only validates that each individual action value
is either "pin" or "move_top", but does not ensure that both actions appear
exactly once across the two expected workspace.action calls. To fix this, track
the action values across multiple calls (using an instance variable or similar
state mechanism) and add a final assertion that verifies one call had action
"pin" and the other had action "move_top", preventing regressions where the same
action could be sent twice.
🤖 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 `@CLI/CMUXCLI`+SSHStartupScripts.swift:
- Around line 236-241: The shell script loses the exit status because
cmux_status is unset on the line containing "unset cmux_tmp cmux_status" before
the "exit $cmux_status" command is executed, causing the variable to expand to
empty and exit to default to 0. Reorder the commands so that "exit $cmux_status"
is executed before cmux_status is unset, preserving the actual exit status from
the inner script. Move the exit command to run before any unset operations that
affect cmux_status.

---

Outside diff comments:
In `@Sources/Cloud/VMClientSocketCommands.swift`:
- Around line 132-148: In the socketWorkerAttachInfoPayload method, the line
that sets payload["transport"] = "ssh" is redundant because the transport field
from VMSSHEndpoint is already set to "ssh" in the socketWorkerSSHInfoPayload
call. Since endpoint.transport is immutable and always "ssh", remove the
redundant override assignment in the socketWorkerAttachInfoPayload method to
eliminate confusing code that appears to serve a purpose but doesn't.

---

Duplicate comments:
In `@cmuxTests/VMDefaultCloudCommandTests.swift`:
- Around line 214-220: The workspace.action handler in the "workspace.action"
case accepts either "pin" or "move_top" action but does not verify that both
actions are invoked in the correct order. Instead of using a simple OR assertion
with `XCTAssertTrue(action == "pin" || action == "move_top")`, track each action
invocation in a collection or state variable and add assertions to verify that
both "pin" and "move_top" are called sequentially in the expected order. This
ensures the test validates the complete expected behavior rather than just
accepting either action independently.
- Around line 76-82: The test for the "workspace.action" case only validates
that each individual action value is either "pin" or "move_top", but does not
ensure that both actions appear exactly once across the two expected
workspace.action calls. To fix this, track the action values across multiple
calls (using an instance variable or similar state mechanism) and add a final
assertion that verifies one call had action "pin" and the other had action
"move_top", preventing regressions where the same action could be sent twice.
🪄 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: d44f4407-a35c-4b9f-a73a-de8c39e98adc

📥 Commits

Reviewing files that changed from the base of the PR and between dd76540 and ab40778.

📒 Files selected for processing (11)
  • CLI/CMUXCLI+SSHStartupScripts.swift
  • CLI/cmux.swift
  • Resources/Localizable.xcstrings
  • Sources/Cloud/VMClientSocketCommands.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/VMDefaultCloudCommandTests.swift
  • cmuxTests/VMSSHCommandTests.swift
  • web/services/vms/providerGateway.ts
  • web/services/vms/repository.ts
  • web/services/vms/workflows.ts
  • web/tests/vm-workflows.test.ts

Comment thread CLI/CMUXCLI+SSHStartupScripts.swift
@lawrencecchen
lawrencecchen force-pushed the task-pinned-sshd-freestyle-cloud branch from 8e3a24a to bba505e Compare June 19, 2026 01:00
Comment thread CLI/cmux.swift
@lawrencecchen
lawrencecchen force-pushed the task-pinned-sshd-freestyle-cloud branch 2 times, most recently from 2c48e65 to 7e1e0f1 Compare June 19, 2026 01:15
Comment thread CLI/CMUXCLI+SSHStartupScripts.swift

@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/Cloud/VMClientSocketCommands.swift (1)

146-148: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve endpoint transport in attach SSH payloads.

Line 147 hardcodes "ssh" and overrides the new endpoint.transport value, which can break transport-based routing on vm.attach_info consumers.

Suggested fix
         case .ssh(let ssh):
-            var payload = socketWorkerSSHInfoPayload(ssh)
-            payload["transport"] = "ssh"
-            return payload
+            return socketWorkerSSHInfoPayload(ssh)
🤖 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/Cloud/VMClientSocketCommands.swift` around lines 146 - 148, The
transport value in the payload for the SSH info is hardcoded to "ssh" on line
147 within the function containing socketWorkerSSHInfoPayload(ssh), which
overrides the actual endpoint.transport value and breaks transport-based
routing. Replace the hardcoded "ssh" string assignment to payload["transport"]
with the actual transport value from the endpoint object to preserve the correct
transport type in the attach SSH payload.
♻️ Duplicate comments (1)
CLI/CMUXCLI+SSHStartupScripts.swift (1)

260-265: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Exit status lost: cmux_status is unset before exit uses it.

Line 263 unsets cmux_status, then line 265 attempts exit $cmux_status. After unset, the variable expands to empty and exit defaults to 0, discarding the inner script's actual exit status.

🐛 Proposed fix
             "cmux_status=$?",
             "trap - EXIT HUP INT TERM",
             "cmux_cleanup",
-            "unset cmux_tmp cmux_status",
+            "unset cmux_tmp",
             "unset -f cmux_cleanup 2>/dev/null || true",
             "exit $cmux_status",
+            "unset cmux_status 2>/dev/null || true",

Or capture exit code before cleanup:

             "cmux_status=$?",
             "trap - EXIT HUP INT TERM",
             "cmux_cleanup",
-            "unset cmux_tmp cmux_status",
-            "unset -f cmux_cleanup 2>/dev/null || true",
-            "exit $cmux_status",
+            "cmux_exit=$cmux_status",
+            "unset cmux_tmp cmux_status",
+            "unset -f cmux_cleanup 2>/dev/null || true",
+            "exit $cmux_exit",
🤖 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 `@CLI/CMUXCLI`+SSHStartupScripts.swift around lines 260 - 265, The exit status
is being lost because `cmux_status` is unset in the cleanup section before it is
used in the exit command. The variable `cmux_status` should be used in the exit
statement before it gets unset. Rearrange the order of operations in the cleanup
sequence so that `exit $cmux_status` is executed before the line that unsets
`cmux_status` and the cleanup function. Alternatively, save the exit status to a
temporary location before cleanup begins, then use that saved value in the final
exit command.
🤖 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/Cloud/VMClientSocketCommands.swift`:
- Around line 146-148: The transport value in the payload for the SSH info is
hardcoded to "ssh" on line 147 within the function containing
socketWorkerSSHInfoPayload(ssh), which overrides the actual endpoint.transport
value and breaks transport-based routing. Replace the hardcoded "ssh" string
assignment to payload["transport"] with the actual transport value from the
endpoint object to preserve the correct transport type in the attach SSH
payload.

---

Duplicate comments:
In `@CLI/CMUXCLI`+SSHStartupScripts.swift:
- Around line 260-265: The exit status is being lost because `cmux_status` is
unset in the cleanup section before it is used in the exit command. The variable
`cmux_status` should be used in the exit statement before it gets unset.
Rearrange the order of operations in the cleanup sequence so that `exit
$cmux_status` is executed before the line that unsets `cmux_status` and the
cleanup function. Alternatively, save the exit status to a temporary location
before cleanup begins, then use that saved value in the final exit command.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: c6384a0e-8b66-47ec-8fc3-515d1946c4a5

📥 Commits

Reviewing files that changed from the base of the PR and between 8e3a24a and 2c48e65.

📒 Files selected for processing (11)
  • CLI/CMUXCLI+SSHStartupScripts.swift
  • CLI/cmux.swift
  • Resources/Localizable.xcstrings
  • Sources/Cloud/VMClientSocketCommands.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/VMDefaultCloudCommandTests.swift
  • cmuxTests/VMSSHCommandTests.swift
  • web/services/vms/providerGateway.ts
  • web/services/vms/repository.ts
  • web/services/vms/workflows.ts
  • web/tests/vm-workflows.test.ts

Comment thread CLI/cmux.swift
@lawrencecchen
lawrencecchen force-pushed the task-pinned-sshd-freestyle-cloud branch from fe83cf6 to 37fe0fe Compare June 19, 2026 04:31
lawrencecchen and others added 4 commits July 6, 2026 19:06
… menu item highlight

The merge removed main's tabContextForkConversationOpenAvailabilityProvider
wiring on a wrong submodule-API assumption (the bonsplit pointer matches
main, which compiles with it). The custom mouse-down menu item's highlight
drew a sharp rectangle; it now draws the native rounded selection shape.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Round-4 localization audit output: debug menu/window titles through
String(localized:) with catalog entries for all supported locales.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…style-cloud

# Conflicts:
#	Packages/macOS/CmuxRemoteDaemon/Sources/CmuxRemoteDaemon/Client/RemoteDaemonRPCClient.swift
#	Sources/Workspace.swift

@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 using default effort and found 1 potential issue.

There are 3 total unresolved issues (including 2 from previous reviews).

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 bc7b967. Configure here.

" printf '\\n\\033[31m[cmux] ssh exited with status %s.\\033[0m\\n\\033[2m[cmux] the remote VM may have been paused, destroyed, or lost network.\\033[0m\\n\\033[2m[cmux] press Enter to close this pane.\\033[0m\\n' \"$cmux_ssh_status\" >&2 || true",
" IFS= read -r _cmux_dismiss_key 2>/dev/null || true",
"fi",
"exit $cmux_ssh_status",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Manual SSH reconnect removed

Medium Severity

The refactored SSH startup script drops the post-failure manual reconnect path (cmux_ssh_remote_reconnect / press r to reconnect) that the previous inline implementation supported. After auto-retries exhaust, users only get a dismiss prompt and cannot trigger workspace.remote.reconnect from the terminal.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit bc7b967. Configure here.

lawrencecchen and others added 2 commits July 6, 2026 21:43
…ct for new vm verbs

The SplitButtonLayoutDebugWindowController/View referenced the
#if DEBUG-guarded TitlebarNewWorkspaceCloudSplitButtonDebugSettings but
were not themselves DEBUG-gated, so the Release build could not resolve
the type (Debug compiled fine). Wrapped the whole debug-window block in
#if DEBUG, matching its only invocation site. Also updated
docs/cli-contract.md's vm/cloud --help probes to the branch's verb set
(base|status|snapshot|fork|restore added).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
lawrencecchen and others added 4 commits July 7, 2026 04:55
…-declaration)

A prior merge kept the branch's inline menu implementation in
AppDelegate.swift while main had extracted it into
AppDelegate+NewWorkspaceContextMenu.swift, so several symbols were
declared twice — Debug tolerated it but Release failed with invalid
redeclaration / selector conflict. Unified both feature sets in the
extension file (branch's Cloud VM section + section ordering + dropdown
position overload; main's per-item option-delete alternates + workspace
action affordances) and removed the inline copies from AppDelegate.swift.
Release and Debug both compile clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Provisioning verbs (create, base open/reset, fork, restore) now return a
402 vm_requires_pro with an upgrade link when the caller is not on a paid
plan. Paid = pro or team (imported from billing/pro.ts). Ships dark: the
gate only enforces when CMUX_VM_REQUIRE_PRO is truthy (opt-in, inverse of
the CMUX_VM_CREATE_ENABLED convention), so free users keep provisioning
until product flips the env. Management verbs (list/rm/exec/shell/ssh/
attach) are intentionally NOT gated so a downgraded user can still see and
wind down existing VMs. No Swift/CLI change — the CLI already renders the
server error action text.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

This branch was successfully deployed

1 active deployment
Preview – cmux — 22c389ef Deployed Jul 7, 2026 by vercel[bot]
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