6.7 KiB
6.7 KiB
Remote SSH Living Spec
Last updated: February 21, 2026
Tracking issue: https://github.com/manaflow-ai/cmux/issues/151
Primary PR: https://github.com/manaflow-ai/cmux/pull/239
This document is the working source of truth for:
- what is implemented now
- what is intentionally temporary
- what must be built next
1. Document Type
This is a living implementation spec (also called an execution spec): a spec-level document with status tracking (DONE, IN PROGRESS, TODO) and acceptance tests.
2. Objective
cmux ssh should provide:
- durable remote terminals with reconnect/reuse
- browser traffic that egresses from the remote host via proxying
- tmux-style PTY resize semantics (
smallest screen wins)
3. Current State (Implemented)
3.1 Remote Workspace + Reconnect UX
DONEcmux sshcreates remote-tagged workspaces and does not require--name.DONEscoped shell niceties are applied only forcmux sshlaunches.DONEcontext menu actions exist for remote workspaces (Reconnect Workspace(s),Disconnect Workspace(s)).DONEsocket API includesworkspace.remote.reconnect.
3.2 Bootstrap + Daemon
DONElocal app probes remote platform, builds/uploadscmuxd-remote, and runsserve --stdio.DONEdaemonhellohandshake is enforced.DONEbootstrap/probe failures surface actionable details.
3.3 Error Surfacing
DONEremote errors are surfaced in sidebar status + logs + notifications.DONEreconnect retry count/time is included in surfaced error text (for example,retry 1 in 4s).
3.4 Existing Temporary Behavior (To Remove)
TEMPORARYcurrent implementation probes remote listening ports and mirrors them locally with SSH-L.TEMPORARYsidebar shows local bind conflicts (SSH port conflicts ...) caused by that mirroring path.TARGETbrowser path must no longer depend on per-port mirroring.
4. Target Architecture (No Port Mirroring)
4.1 Browser Networking Path
- One local proxy endpoint per SSH transport (not per workspace, not per detected port).
- Proxy endpoint supports SOCKS5 and HTTP CONNECT.
- Browser panels in remote workspaces are auto-wired to this proxy endpoint.
- Browser panels in local workspaces are not force-proxied.
4.2 WKWebView Wiring
- Use workspace/browser scoped
WKWebsiteDataStore.proxyConfigurations. - Prefer SOCKS5 proxy config.
- Keep HTTP CONNECT proxy config as fallback.
- Re-apply/validate proxy config after reconnect.
4.3 Remote Daemon + Transport
- Extend
cmuxd-remotebeyondhello/pingwith proxy stream RPC (proxy.open,proxy.close). - Local side runs a transport-scoped proxy broker and multiplexes proxy streams over SSH stdio transport.
- Remove remote service-port discovery/probing from browser routing path.
4.4 Explicit Non-Goal
- Automatic mirroring of every remote listening port to local loopback is not a goal for browser support.
5. PTY Resize Semantics (tmux-style)
5.1 Core Rule
For each session with multiple attachments, the effective PTY size is:
cols = min(cols_i over attached clients)rows = min(rows_i over attached clients)
This is the smallest screen wins rule.
5.2 State Model
Per session track:
- set of active attachments
{attachment_id -> cols, rows, updated_at} - effective size currently applied to PTY
- last-known size when temporarily unattached
5.3 Recompute Triggers
Recompute effective size on:
- attachment create
- attachment detach
- resize event from any attachment
- reconnect reattach
5.4 Correctness Requirements
- Never shrink history because of UI relayout noise; only PTY viewport changes.
- On reconnect, reuse persisted session and recompute from active attachments.
- If no attachments remain, keep last-known PTY size (do not force 80x24 reset).
6. Milestones (Living Status)
| ID | Milestone | Status | Notes |
|---|---|---|---|
| M-001 | cmux ssh workspace creation + metadata + optional --name |
DONE | Covered by tests_v2/test_ssh_remote_cli_metadata.py |
| M-002 | Remote bootstrap/upload/start + hello handshake | DONE | Current cmuxd-remote is minimal (hello, ping) |
| M-003 | Reconnect/disconnect UX + API + improved error surfacing | DONE | Includes retry count in surfaced errors |
| M-004 | Docker e2e for bootstrap/reconnect shell niceties | DONE | Existing docker tests currently validate mirroring-era path |
| M-005 | Remove automatic remote port mirroring path | TODO | Delete probe/listen mirror loop from WorkspaceRemoteSessionController |
| M-006 | Transport-scoped local proxy broker (SOCKS5 + CONNECT) | TODO | Local component in app/daemon layer |
| M-007 | Remote proxy stream RPC in cmuxd-remote |
TODO | Add proxy.open/close and multiplexed stream handling |
| M-008 | WebView proxy auto-wiring for remote workspaces | TODO | Use WKWebsiteDataStore.proxyConfigurations |
| M-009 | PTY resize coordinator (smallest screen wins) |
TODO | Session-level attachment-size aggregation |
| M-010 | Resize + proxy reconnect e2e test suites | TODO | Add dedicated docker cases for browser proxy + resize |
7. Acceptance Test Matrix (With Status)
7.1 Terminal + Reconnect
| ID | Scenario | Status |
|---|---|---|
| T-001 | baseline remote connect | DONE |
| T-002 | identical host reuse semantics | PARTIAL |
| T-003 | no --name |
DONE |
| T-004 | reconnect API success/error paths | DONE |
| T-005 | retry count visible in daemon error detail | DONE |
7.2 Browser Proxy (Target)
| ID | Scenario | Status |
|---|---|---|
| W-001 | remote workspace browser auto-proxied | TODO |
| W-002 | browser egress IP equals remote host IP | TODO |
| W-003 | websocket via SOCKS5/CONNECT through remote daemon | TODO |
| W-004 | reconnect restores browser proxy path automatically | TODO |
| W-005 | local proxy bind conflict yields structured proxy_unavailable |
TODO |
7.3 Resize
| ID | Scenario | Status |
|---|---|---|
| RZ-001 | two attachments, smallest wins | TODO |
| RZ-002 | grow one attachment, PTY stays bounded by smallest | TODO |
| RZ-003 | detach smallest, PTY expands to next smallest | TODO |
| RZ-004 | reconnect preserves session + applies recomputed size | TODO |
8. Removal Checklist (Port Mirroring)
Before declaring browser proxying complete:
- remove remote port probe loop and
-Lauto-forward orchestration - remove mirror-specific sidebar conflict messaging as default remote behavior
- replace mirroring tests with browser-proxy e2e tests
- keep optional explicit user-driven forwarding as separate feature only if needed
9. Open Decisions
- Proxy auth policy for local broker (
nonevs optional credentials). - Reconnect backoff profile and max retry budget.
- Browser data-store isolation policy for remote vs local workspaces.