Skip to content

Add remote-aware resume bindings for SSH workspaces - #8441

Merged
austinywang merged 29 commits into
mainfrom
issue-7989-remote-resume-bindings
Jul 21, 2026
Merged

austinywang merged 29 commits into
mainfrom
issue-7989-remote-resume-bindings

Conversation

@austinywang

@austinywang austinywang commented Jul 19, 2026 •

Copy link
Copy Markdown
Contributor

Summary\n- persist an explicit local or persistent-SSH resume launch flavor with owning workspace, surface, and PTY session IDs\n- mark relayed SessionStart registrations after restored-ID alias rewriting and expose remote context through surface.resume.get\n- reattach live remote PTYs without duplication, while recreating missing PTYs with the approved resume command executed through the remote shell bootstrap\n- preserve remote cwd/environment sanitization and retarget ownership after restore or surface moves\n\n## Tests and validation\n- added behavior coverage for relayed registration, restored aliases, remote cwd/environment sanitization, persistence, and live-versus-missing PTY restore\n- kept the required two-commit test-then-fix history\n- ./scripts/reload.sh --tag issue-7989-remote-resume-bindings --launch\n- ./scripts/check-pbxproj.sh\n- ./scripts/lint-pbxproj-test-wiring.sh\n- python3 scripts/swift_warning_budget.py --log /tmp/cmux-reload-issue-7989-remote-resume-bindings.log\n- git diff --check\n- Swift parse checks for all changed files\n- cmux-unit build-for-testing succeeded on final HEAD 4e6fc46\n- focused final-HEAD tests passed: 18 tests in RemoteResumeBindingTests, RemoteInitialCommandBootstrapFailureTests, and ShellStartupMatrixTests\n- tagged-runtime managed-SSH verification passed both live-PTY reattach (same PID, zero resume reruns) and missing-PTY recreation (remote-only resume exactly once)\n- binding retargeting, spaced cwd/environment, secret filtering, focus preservation, forged-provenance rejection, and reconnect stability were verified through the tag-bound socket\n- cmux-unit and focused tests were run locally; XCUITests were not required for this socket/remote-runtime path\n\n## Localization\n- no new UI, settings, help, or documentation text\n- the new validation path reuses an existing English/Japanese localized socket error key; the catalog parses successfully\n\nCloses #7989


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


Summary by cubic

Adds remote-aware SSH resume bindings with a persisted launch flavor and HMAC-authenticated relay. Live PTYs reattach; ended/missing PTYs recreate and run the approved resume exactly once via the remote bootstrap, including before unsupported shells. Legacy snapshots without a workspace ID migrate to persistent SSH and retarget on restore/detach (Linear #7989).

  • New Features

    • Persist launchFlavor (local or remote_ssh) and expose remote context; surface.resume.get returns execution_location, remote_workspace_id, remote_surface_id, remote_pty_session_id.
    • surface.resume.set accepts authenticated relayed inputs; the command rewriter injects _cmux_remote_workspace_id plus an HMAC and classifies methods from decoded JSON.
    • SSH attach passes a remoteCommand so the bootstrap runs the resume only on session recreation; it now runs before unsupported shells and only once.
  • Bug Fixes

    • Validate and reject missing/invalid _cmux_remote_workspace_id in control socket requests.
    • Harden relay provenance authentication for surface.resume.set using the relay token while preserving shell selection and single-run resume behavior.

Written for commit fefcb08. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features
    • Added durable persistent remote SSH resume for PTY reattachment, including remote workspace/surface/PTY linkage.
    • Resume bindings now include execution_location and remote linkage identifiers for reconnect-safe retargeting.
    • Restored remote login shells can run an optional initial command before launching the shell.
  • Bug Fixes
    • Improved validation with a clearer localized error when the remote workspace identifier is missing or invalid.
    • Preserve and migrate resume launch flavor correctly across session restore, with hardened relayed resume authentication and retargeting.
  • Tests
    • Added end-to-end coverage for remote resume, persistent restore, and legacy migration.

@coderabbitai

coderabbitai Bot commented Jul 19, 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

This change adds remote execution metadata to surface resume bindings, authenticates relayed resume commands, persists remote launch context, and restores persistent SSH PTY sessions with shell-specific bootstrap commands. End-to-end tests cover relayed registration and persistent restore behavior.

Changes

Remote resume lifecycle

Layer / File(s) Summary
Resume model and control-socket propagation
Sources/SurfaceResume*.swift, Sources/SessionPersistence.swift, Sources/TabManager.swift, Sources/TerminalController+ControlSurfaceContext.swift, Packages/macOS/CmuxControlSocket/...
Resume bindings encode launch flavor and remote context, preserve it during persistence and retargeting, validate remote workspace IDs, and expose execution and remote identifiers in control responses.
Relay command rewriting and authentication
Sources/Workspace+RemoteRelayCommandRewrite.swift, Sources/WorkspaceRemoteRelayCommandRewriter.swift, Sources/Workspace+RemoteSessionLifecycle.swift, Sources/Workspace.swift
Relayed JSON parameters are recursively remapped, surface.resume.set commands receive remote workspace ownership, and relay parameters are authenticated with HMAC-SHA256.
Persistent SSH restore and attach commands
Sources/Workspace*.swift, Sources/RemoteInteractiveShellBootstrapBuilder.swift, Sources/SSHPTYAttachStartupCommandBuilder.swift, Sources/SessionRemoteWorkspaceSnapshot+Restore.swift
Persistent SSH contexts are validated and approved, restore commands flow through PTY attachment, and shell-specific bootstrap logic runs optional initial commands.
Remote resume validation and build integration
cmuxTests/RemoteResumeBindingTests.swift, cmux.xcodeproj/project.pbxproj
End-to-end tests validate relayed registration, authentication rejection, persistent restore, and PTY reattachment; new implementation and test sources are registered in the Xcode project.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related issues

  • manaflow-ai/cmux issue 7989 — Covers the remote-aware resume binding and persistent SSH restore behavior implemented here.

Possibly related PRs

Sequence Diagram(s)

sequenceDiagram
  participant Relay
  participant WorkspaceRemoteRelayCommandRewriter
  participant TerminalController
  participant Workspace
  participant RemoteShellBootstrap
  Relay->>WorkspaceRemoteRelayCommandRewriter: send surface.resume.set payload
  WorkspaceRemoteRelayCommandRewriter->>TerminalController: rewrite identifiers and add authentication
  TerminalController->>Workspace: validate and register persistent SSH resume binding
  Workspace->>RemoteShellBootstrap: build attach command with initial resume command
  RemoteShellBootstrap-->>Workspace: execute shell-specific remote resume bootstrap
Loading

Important

Pre-merge checks failed

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

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
Cmux Swift Package Boundaries ❌ Error Root Sources added reusable resume/auth/persistence logic, including value types and HMAC relay rewriting, which the boundary rules say belong behind a SwiftPM package. Move the surface-resume value types and relay rewriter into a small SwiftPM package (e.g. CmuxRemoteWorkspace) and keep Workspace/SessionPersistence as app-composition wrappers.
Docstring Coverage ⚠️ Warning Docstring coverage is 8.47% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (23 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 New value types/utilities are plain Sendable structs/enums, and all resume/control call sites stay on the main actor or are explicitly nonisolated where needed.
Cmux Swift Blocking Runtime ✅ Passed The changed production Swift is pure data/script plumbing; I found no new semaphores, waits, syncs, sleeps, asyncAfter, polling, or locks in the modified runtime paths.
Cmux Browser Automation Off-Main ✅ Passed Diff only adds a shell-startup regression test and helper parameter; no browser.* commands, WebKit/AppKit waits, or routing changes are present.
Cmux Expensive Synchronous Load ✅ Passed No new corpus load or large-file parse was added; the existing warm-cache RestorableAgentSessionIndex.load() call sites are unchanged.
Cmux Cache Substitution Correctness ✅ Passed PASS: snapshot paths keep event-driven PTY caches and cold fallbacks; no fresh authoritative read was swapped for an opportunistic cache.
Cmux No Hacky Sleeps ✅ Passed HEAD changes only cmuxTests/ShellStartupMatrixTests.swift (Swift test code); no non-Swift runtime/build scripts were modified, so the no-hacky-sleeps rule is not triggered.
Cmux Algorithmic Complexity ✅ Passed No new scalable-collection rescans: remote resume uses Set/dictionary lookups, and the JSON/HMAC work is per-command payload only.
Cmux Swift Concurrency ✅ Passed PASS: The only changed file is a test, and the patch adds synchronous assertions/fixture plumbing only; no new legacy DispatchQueue/Combine/completion-handler or fire-and-forget Task patterns appear.
Cmux Swift @Concurrent ✅ Passed No touched Swift code adds misused @concurrent or nonisolated async; the new helpers are synchronous/pure or stay on @MainActor.
Cmux Swiftpm Lockfiles ✅ Passed The PR only adds source-file registrations to cmux.xcodeproj; there are no .gitignore or Package.resolved changes, and no SwiftPM package-reference edits.
Cmux Swift Logging ✅ Passed No added or changed Swift logging APIs in the patch; existing debug-only NSLog calls remain unchanged.
Cmux User-Facing Error Privacy ✅ Passed Added user-facing errors stay generic; no secrets, tokens, or raw upstream messages are leaked in new error copy.
Cmux Full Internationalization ✅ Passed The only new user-facing string uses an existing localized key with en/ja translations; no catalog or locale files were changed.
Cmux Swiftui State Layout ✅ Passed Only changed file is a test suite (cmuxTests/ShellStartupMatrixTests.swift); no SwiftUI views, ObservableObject/@published, GeometryReader, lazy row stores, or render-time state writes.
Cmux Architecture Rethink ✅ Passed Changes add explicit launchFlavor/remoteContext ownership and auth validation; no new sleeps, polling, locks, observers, or duplicate lifecycle wiring.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed Only window code in the diff is a test-only NSWindow helper with a cmux.main. identifier; no production NSWindowController/WindowGroup or cmuxAuxiliaryWindowIdentifiers changes.
Cmux Source Artifacts ✅ Passed Changed paths are source, test, project, or config files; no logs, caches, build output, temp dirs, or other artifact paths were added.
Cmux No Test Or Debug Seam In Production Source ✅ Passed Changed production files add remote-resume runtime logic only; I found no new #if DEBUG/ForTesting/TestHook-style seam or wrapper accessor in the modified Sources code.
Cmux No Ambient Global State ✅ Passed No new ambient global state or new singleton surface appears in the changed production Swift files; the added behavior lives on existing types/extensions.
Title check ✅ Passed The title is concise and accurately summarizes the primary change: remote-aware resume bindings for SSH workspaces.
Description check ✅ Passed The description covers summary and testing well, but it omits the Demo Video, Review Trigger, and Checklist sections from the template.
✨ 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-7989-remote-resume-bindings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Jul 19, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds remote-aware SSH resume bindings: a new SurfaceResumeLaunchFlavor enum persists whether a saved resume command belongs to a local or persistent-SSH session, relay surface.resume.set commands are HMAC-authenticated using the relay token before being accepted as persistent-SSH bindings, and the SSH PTY attach path is extended to pass a base64-encoded resume command for one-shot execution on PTY recreation.

  • Persistence & migration: SurfaceResumeBindingSnapshot gains a launchFlavor field (Codable) with a wasDecodedWithoutLaunchFlavor sentinel for automatic legacy migration to .persistentSSH on restore.
  • Authentication: WorkspaceRemoteRelayCommandRewriter now HMAC-signs relay surface.resume.set commands (sorted-keys JSON payload over HMAC-SHA256 using the relay token); the socket coordinator validates provenance before registering a remote binding.
  • PTY reattach/recreate: Live PTYs are reattached without re-running the resume; missing PTYs receive the approved resume command via --command-b64 in the SSH attach bootstrap, with the one-shot guarantee enforced by the CLI and shell bootstrap state key.

Confidence Score: 5/5

Safe to merge; all guard chains correctly enforce workspace-ID matching, HMAC authentication, and persistent-SSH configuration prerequisites before registering a remote resume binding.

The relay authentication path is correctly symmetric between signing and verification. The SurfaceResumeRemoteContext.matches nil-empty guard fix from the prior review is confirmed. Session-restore refactoring moves restoredRemotePTYSessionID earlier so binding migration works correctly, and the launchFlavor == .local guard suppresses local launches for remote-flavored bindings. One silent-degradation edge case was noted but has no security or correctness impact.

No files require special attention. Sources/Workspace.swift has the densest logic change and is worth a careful read during merge.

Important Files Changed

Filename Overview
Sources/Workspace.swift Session-restore path refactored: restoredRemotePTYSessionID computed earlier, locatedResumeBinding migration applied before approval, restoredBindingLaunch suppressed for non-local flavors, and ownership retargeted on restore.
Sources/WorkspaceRemoteRelayCommandRewriter.swift New HMAC-SHA256 signing/verification for relay surface.resume.set commands. Authentication logic is sound; custom hex codec avoids per-call formatters.
Sources/Workspace+RemoteRelayCommandRewrite.swift Relay command alias rewriting and workspace-ID injection extracted from Workspace.swift. Logic unchanged; nonisolated static marking is correct.
Sources/Workspace+RemoteSurfaceResumeBinding.swift Adds migration, context resolution, and approved-command retrieval for persistent-SSH resume bindings.
Sources/SurfaceResumeRemoteContext.swift matches() now requires both normalized session IDs to be non-nil — addresses the previous nil-equality false-positive.
Sources/SurfaceResumeLaunchFlavor.swift New Codable enum for local vs persistentSSH launch flavors.
Sources/SurfaceResumeBindingSnapshot+Remote.swift Adds migration, persistent-SSH registration, and retargeting on SurfaceResumeBindingSnapshot.
Sources/SessionPersistence.swift Adds launchFlavor CodingKey and wasDecodedWithoutLaunchFlavor sentinel for legacy migration.
Sources/TerminalController+ControlSurfaceContext4.swift Socket handler validates relay provenance (workspace ID + HMAC) before registering a persistent-SSH binding.
cmuxTests/RemoteResumeBindingTests.swift 1172-line test file covering relay registration, alias rewriting, legacy migration, authentication rejection, and live vs. missing PTY restore.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant R as Remote Shell (relay)
    participant RW as WorkspaceRemoteRelayCommandRewriter
    participant CS as Control Socket Handler
    participant TC as TerminalController
    participant WS as Workspace

    R->>RW: surface.resume.set (relay command)
    RW->>RW: rewriteRemoteRelayCommandLineAndExtractMethod() injects _cmux_remote_workspace_id
    RW->>RW: authenticatedRemoteResumeCommandLine() HMAC-SHA256 sign with relay token
    RW->>CS: signed surface.resume.set + _cmux_remote_relay_authentication_code
    CS->>CS: validate _cmux_remote_workspace_id (UUID)
    CS->>TC: controlSurfaceResumeSet(inputs, relayParameters)
    TC->>TC: "guard remoteWorkspaceID == target.workspace.id"
    TC->>WS: authenticatesRemoteResumeParameters(relayParams, relayToken)
    WS-->>TC: Bool (HMAC verify)
    TC->>WS: persistentSSHResumeContext(panelID)
    WS-->>TC: SurfaceResumeRemoteContext
    TC->>TC: binding.registeredForPersistentSSH(context)
    TC->>WS: setSurfaceResumeBinding(locatedBinding)
    Note over WS: On session restore
    WS->>WS: migratingLegacyPersistentSSHResumeBinding()
    WS->>WS: persistentSSHResumeCommand() base64 resume cmd
    WS->>WS: remotePTYAttachStartupCommand(sessionID, remoteCommand)
    WS->>R: ssh-pty-attach --command-b64 resume --require-existing
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant R as Remote Shell (relay)
    participant RW as WorkspaceRemoteRelayCommandRewriter
    participant CS as Control Socket Handler
    participant TC as TerminalController
    participant WS as Workspace

    R->>RW: surface.resume.set (relay command)
    RW->>RW: rewriteRemoteRelayCommandLineAndExtractMethod() injects _cmux_remote_workspace_id
    RW->>RW: authenticatedRemoteResumeCommandLine() HMAC-SHA256 sign with relay token
    RW->>CS: signed surface.resume.set + _cmux_remote_relay_authentication_code
    CS->>CS: validate _cmux_remote_workspace_id (UUID)
    CS->>TC: controlSurfaceResumeSet(inputs, relayParameters)
    TC->>TC: "guard remoteWorkspaceID == target.workspace.id"
    TC->>WS: authenticatesRemoteResumeParameters(relayParams, relayToken)
    WS-->>TC: Bool (HMAC verify)
    TC->>WS: persistentSSHResumeContext(panelID)
    WS-->>TC: SurfaceResumeRemoteContext
    TC->>TC: binding.registeredForPersistentSSH(context)
    TC->>WS: setSurfaceResumeBinding(locatedBinding)
    Note over WS: On session restore
    WS->>WS: migratingLegacyPersistentSSHResumeBinding()
    WS->>WS: persistentSSHResumeCommand() base64 resume cmd
    WS->>WS: remotePTYAttachStartupCommand(sessionID, remoteCommand)
    WS->>R: ssh-pty-attach --command-b64 resume --require-existing
Loading

Reviews (13): Last reviewed commit: "Fix remote resume verifier assertions" | Re-trigger Greptile

Comment thread Sources/SurfaceResumeRemoteContext.swift
Comment thread Sources/WorkspaceRemoteRelayCommandRewriter.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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/Surface/ControlCommandCoordinator`+Surface3.swift:
- Around line 50-59: Update the invalid workspace ID error in the surface split
validation to use the plain string “Missing or invalid workspace_id” directly
instead of String(localized:). Keep the existing invalid_params response
structure and surrounding validation behavior unchanged.

In `@Sources/RemoteInteractiveShellBootstrapBuilder.swift`:
- Around line 139-170: Add a concise inline comment in the `.bash` resumed-shell
branch of `loginShellLaunchLine`, immediately before the nested command’s
`--rcfile "$CMUX_SHELL_INTEGRATION_DIR/.bashrc"` reference, documenting that the
inner freshly exec’d shell must use the exported variable while the outer shell
uses `$cmux_shell_dir`.
🪄 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: f6ed0281-9af3-4f04-9bb4-415db096b3d1

📥 Commits

Reviewing files that changed from the base of the PR and between 6849b93 and cd31bb2.

📒 Files selected for processing (21)
  • Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/Surface/ControlCommandCoordinator+Surface3.swift
  • Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/Surface/ControlSurfaceResumeBinding.swift
  • Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/Surface/ControlSurfaceResumeSetInputs.swift
  • Sources/RemoteInteractiveShellBootstrapBuilder.swift
  • Sources/SSHPTYAttachStartupCommandBuilder.swift
  • Sources/SessionPersistence.swift
  • Sources/SessionRemoteWorkspaceSnapshot+Restore.swift
  • Sources/SurfaceResumeBindingSnapshot+Remote.swift
  • Sources/SurfaceResumeLaunchFlavor.swift
  • Sources/SurfaceResumeRemoteContext.swift
  • Sources/TabManager.swift
  • Sources/TerminalController+ControlSurfaceContext.swift
  • Sources/TerminalController+ControlSurfaceContext4.swift
  • Sources/Workspace+PersistentRemotePTYReattach.swift
  • Sources/Workspace+RemoteRelayCommandRewrite.swift
  • Sources/Workspace+RemoteSessionLifecycle.swift
  • Sources/Workspace+RemoteSurfaceResumeBinding.swift
  • Sources/Workspace.swift
  • Sources/WorkspaceRemoteRelayCommandRewriter.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/RemoteResumeBindingTests.swift

Comment thread Sources/RemoteInteractiveShellBootstrapBuilder.swift Outdated
@cursor

cursor Bot commented Jul 19, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@coderabbitai coderabbitai Bot 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/WorkspaceRemoteRelayCommandRewriter.swift (1)

8-29: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Detect surface.resume.set via parsed JSON, not a raw byte substring search.

authenticatesRemoteResume (Line 20) is decided by searching the raw, unparsed command-line bytes for the quoted string "surface.resume.set". This gates whether remoteWorkspaceID gets forwarded into Workspace.rewriteRemoteRelayCommandLine for any command whose bytes happen to contain that substring (e.g., embedded in an unrelated field like a command value), even though the actual JSON method might differ. authenticatedRemoteResumeCommandLine below already parses the JSON and checks request["method"] properly — reusing that parse for the initial gate (instead of a second, less reliable heuristic) would remove this fragility. Also consider renaming authenticatesRemoteResume since at that point it only means "looks like a resume-set request," not "has been authenticated."

♻️ Sketch: gate on the parsed method instead of a byte search
-        let authenticatesRemoteResume = commandLine.range(of: Self.remoteResumeMethodNeedle) != nil
+        let isSurfaceResumeSet = Self.parsedMethod(of: commandLine) == "surface.resume.set"
         let rewritten = Workspace.rewriteRemoteRelayCommandLine(
             commandLine,
             workspaceAliases: workspaceAliases,
             surfaceAliases: surfaceAliases,
-            remoteWorkspaceID: authenticatesRemoteResume ? remoteWorkspaceID : nil
+            remoteWorkspaceID: isSurfaceResumeSet ? remoteWorkspaceID : nil
         )
-        guard authenticatesRemoteResume else { return rewritten }
+        guard isSurfaceResumeSet else { return rewritten }
         return authenticatedRemoteResumeCommandLine(rewritten)
🤖 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/WorkspaceRemoteRelayCommandRewriter.swift` around lines 8 - 29,
Replace the raw-byte `remoteResumeMethodNeedle` search in
`rewriteRemoteRelayCommandLine` with parsed-JSON method detection, reusing the
existing parsing and `request["method"]` check from
`authenticatedRemoteResumeCommandLine` where practical. Rename
`authenticatesRemoteResume` to reflect that it identifies a resume-set request
rather than authentication, and ensure only requests whose actual method is
`surface.resume.set` pass `remoteWorkspaceID` and receive authentication
handling.
🤖 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/WorkspaceRemoteRelayCommandRewriter.swift`:
- Around line 8-29: Replace the raw-byte `remoteResumeMethodNeedle` search in
`rewriteRemoteRelayCommandLine` with parsed-JSON method detection, reusing the
existing parsing and `request["method"]` check from
`authenticatedRemoteResumeCommandLine` where practical. Rename
`authenticatesRemoteResume` to reflect that it identifies a resume-set request
rather than authentication, and ensure only requests whose actual method is
`surface.resume.set` pass `remoteWorkspaceID` and receive authentication
handling.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 55faee6a-7918-45a7-950e-231955798f7c

📥 Commits

Reviewing files that changed from the base of the PR and between 50e2370 and 7a19f20.

📒 Files selected for processing (6)
  • Sources/RemoteInteractiveShellBootstrapBuilder.swift
  • Sources/SurfaceResumeLaunchFlavor.swift
  • Sources/SurfaceResumeRemoteContext.swift
  • Sources/Workspace+RemoteSessionLifecycle.swift
  • Sources/WorkspaceRemoteRelayCommandRewriter.swift
  • cmuxTests/RemoteResumeBindingTests.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)
cmuxTests/RemoteResumeBindingTests.swift (1)

362-376: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove unused parameters from the test helper.

The workspaceID and surfaceID parameters are explicitly ignored with _ = to suppress compiler warnings. Since the assertions correctly check for the __CMUX_WORKSPACE_ID__ and __CMUX_SURFACE_ID__ placeholder literals rather than the actual UUIDs, you can safely remove these unused parameters from the helper and its call sites to clean up the code.

🧹 Proposed refactoring
-    private func expectRemoteResumeBootstrap(
-        _ command: String,
-        workspaceID: UUID,
-        surfaceID: UUID
-    ) throws {
+    private func expectRemoteResumeBootstrap(
+        _ command: String
+    ) throws {
         `#expect`(command.contains("export CMUX_SOCKET_PATH=127.0.0.1:\(relayPort)"), "\(command)")
         `#expect`(command.contains("__CMUX_WORKSPACE_ID__"), "\(command)")
         `#expect`(command.contains("__CMUX_SURFACE_ID__"), "\(command)")
         `#expect`(command.contains("/srv/remote project"), "\(command)")
         `#expect`(command.contains("REMOTE_FLAG=value with spaces"), "\(command)")
         `#expect`(command.contains("session-remote-7989"), "\(command)")
         `#expect`(!command.contains("ANTHROPIC_API_KEY"), "\(command)")
-        _ = workspaceID
-        _ = surfaceID
     }

Update the call sites accordingly:

-        try expectRemoteResumeBootstrap(
-            liveFirstRemoteCommand,
-            workspaceID: restoredWorkspace.id,
-            surfaceID: restoredSurfaceID
-        )
+        try expectRemoteResumeBootstrap(liveFirstRemoteCommand)
🤖 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/RemoteResumeBindingTests.swift` around lines 362 - 376, Remove the
unused workspaceID and surfaceID parameters from expectRemoteResumeBootstrap,
delete the corresponding _ = assignments, and update every call site to pass
only the command argument while preserving the existing placeholder assertions.
🤖 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 `@cmuxTests/RemoteResumeBindingTests.swift`:
- Around line 362-376: Remove the unused workspaceID and surfaceID parameters
from expectRemoteResumeBootstrap, delete the corresponding _ = assignments, and
update every call site to pass only the command argument while preserving the
existing placeholder assertions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 83be6e55-ee6b-4c52-8b1e-201b3d10593e

📥 Commits

Reviewing files that changed from the base of the PR and between 7a19f20 and 81f5f5f.

📒 Files selected for processing (5)
  • Sources/SessionPersistence.swift
  • Sources/SurfaceResumeBindingSnapshot+Remote.swift
  • Sources/Workspace+RemoteSurfaceResumeBinding.swift
  • Sources/Workspace.swift
  • cmuxTests/RemoteResumeBindingTests.swift

@austinywang

austinywang commented Jul 19, 2026 •

Copy link
Copy Markdown
Contributor Author

Correction after the escaped-method red proof:

  • Removing the unused test-helper parameters in 1bb3105c0a remains correct.
  • The simple substring false-positive case was harmless because structural checks still gated provenance injection and signing. However, escaped JSON exposed a real false-negative trust-boundary bypass: surface.resume\u002eset decodes to surface.resume.set in the dispatcher but did not match the raw byte needle, so remote provenance and HMAC were omitted.
  • ca90da27c4 added a behavior regression; remote run 29674879616 executed all 5 RemoteResumeBindingTests and failed only that case on the missing workspace provenance and HMAC assertions.
  • 38585b7f7c removes the byte needle and classifies the decoded top-level method returned by the structural rewrite. Ordinary relay commands parse once; actual resume requests take the additional parse needed to sign the rewritten payload.

Focused final-HEAD reruns are in progress: ShellStartupMatrixTests 29675238385 and RemoteResumeBindingTests 29675238337.

@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 `@cmuxTests/RemoteResumeBindingTests.swift`:
- Line 350: Remove throws from expectRemoteResumeBootstrap and remove try from
its call sites in cmuxTests/RemoteResumeBindingTests.swift at lines 72, 107, and
129; no other behavior changes are needed.
🪄 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: 91e0701c-5b0d-42d4-9432-f985ec79618e

📥 Commits

Reviewing files that changed from the base of the PR and between 81f5f5f and 1bb3105.

📒 Files selected for processing (1)
  • cmuxTests/RemoteResumeBindingTests.swift

Comment thread cmuxTests/RemoteResumeBindingTests.swift
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