Skip to content

Headless server mode and SSH remote environments #245

Closed

Description

@currentbits

Inspired by T3 Code's remote architecture (its biggest differentiator): the backend is a small local server, the UI talks over WebSocket, so 't3 serve' runs headless and phones/other machines connect; SSH-launched environments probe a host, install/reuse the server and port-forward it.

Solenta angle: run the Electron main logic (or a trimmed core) headless on a workstation, pair clients via the QR/one-time-token flow from #151, and treat SSH hosts as first-class environments whose repos appear next to local projects. Tailscale Serve integration would make this zero-config on the tailnet.

Activity

  1. currentbits commented on Aug 17, 2026

    @currentbits
    OwnerAuthor

    User-pain research (round 6): two hard-won lessons for the headless/remote path. (1) Cursor's iOS launch is the cautionary tale — the headline remote-control feature failing at launch turned the story into a bug report; one rock-solid path (approve + steer + notify) beats a broad flaky one. (2) The host staying awake is a universal failure point: ship a keep-awake helper or an explicit 'host asleep' state. (3) Local-first is the unmet demand: 'steer the agents on MY machine with MY MCP servers/Docker/worktrees' — cloud-session mobile apps structurally cannot reach this, Solenta can.

  2. added and removed on Oct 2, 2026
  3. currentbits commented on Oct 2, 2026

    @currentbits
    OwnerAuthor

    Research update (2026-10-02), based on T3 Code source at 54084ae:

    T3 Connections attach a client to one environment server. That host owns projects, threads, worktrees, provider processes, terminals, credentials, and durable state. Direct pairing, SSH forwarding, Tailscale/direct HTTPS, and T3 Connect all reach the same authenticated HTTP/WebSocket RPC server. Connection identity is the environment UUID, not the URL/port; reconnect validates identity and refreshes subscriptions. SSH can install/reuse a pinned remote server and forward its loopback port. T3 Connect brokers discovery/bootstrap rather than proxying normal app traffic. Sources: remote architecture, connection runtime, SSH tunnel.

    Solenta's --serve-web already exposes the live Store/Runner/IPCs via HTTP + WebSocket and keeps the host process alive after its window closes. A loopback server on the work machine plus SSH local forwarding is the shortest path to prove remote execution with remote worktrees. Current SSH project mode only wraps selected commands and leaves Store/UI/terminal semantics local, so it should stay distinct; #1402 tracks its shared-checkout worker defect.

    Recommended sequence:

    1. Two-machine proof: host --serve-web on loopback, SSH forward, viewer browser; verify agent run, isolated worker fork, permission response, artifact, reconnect, and host-side process/worktree ownership.
    2. Native Connections UI: saved host catalog, Electron-managed SSH forward, stable host identity handshake, createWireCoder selection, host-scoped caches and selection. No mutation replay on uncertain disconnect.
    3. Credential/session security and local-vs-host native-action audit. Phone pairing UX: QR code, one-time tokens, revocable device sessions #151 owns one-time pairing and revocation; retain loopback-only SSH route until scoped authorization and TLS are addressed.
    4. Displayless startup/service support only after the connection path is proven. Cloud relay and auto-balancing are later options.

    Full local research report: reports/T3 Code remote connections.md in this checkout. No live two-machine trial has been run yet.

  4. currentbits commented on Oct 2, 2026

    @currentbits
    OwnerAuthor

    Started the native Connections implementation on branch coder/native-remote-connections (commit 8a4791dc). Settings → Connections now saves SSH host profiles, opens a loopback SSH forward to the host's existing --serve-web port, verifies the WebSocket token before displaying the host, and loads the remote UI in an isolated Electron window. The token is entered per connection and is not saved with the profile. SSH exit closes the remote window with an explanation. The existing local desktop stays separate, so its thread cache cannot mix with the remote host.

    Verification: production npm run build; 73 focused renderer tests; 20 existing web-server tests; 2 new SSH connection tests including wrong-token rejection; all pass. This is a manual-connect first slice, not completion of #245: stable host identity, credential storage, automatic reconnect, and unattended host startup remain open.

  5. currentbits commented on Oct 2, 2026

    @currentbits
    OwnerAuthor

    Draft PR for the manual SSH Connections slice: #1408

  6. currentbits commented on Oct 2, 2026

    @currentbits
    OwnerAuthor

    Follow-up on draft PR #1408: added the SSH connection test to the Windows Electron test list (9a84bef2). The latest CI run 37033320972 is green across all six macOS, Ubuntu, and Windows smoke/test jobs. #245 remains in progress for host identity, reconnect, credential storage, and unattended host work.

  7. currentbits commented on Oct 3, 2026

    @currentbits
    OwnerAuthor

    Credential storage slice pushed to draft PR #1408 (32014f94). After a token is verified, it is saved per host:port with Electron safeStorage (OS keychain) in userData/remote-connections.json. The renderer keeps only a tokenSaved hint. A blank token reuses the saved one. A saved token the host rejects is deleted and the user is asked for the current one. Forget removes the saved token. Linux basic_text (plaintext) storage is refused. There is a "Remember token" checkbox, on by default.

    Verification: typecheck clean. Renderer 2641/2641. Electron 4080/4081: the one failure is fetchCodex process lifecycle in provider-usage.test.js, which passes 22/22 in isolation with and without this change, so it is a load flake. Remaining for #245: stable host identity, automatic reconnect, unattended host startup.

  8. currentbits commented on Oct 3, 2026

    @currentbits
    OwnerAuthor

    Reconnect slice pushed to draft PR #1408 (aabec290). An unexpected SSH exit no longer closes the remote window. The tunnel is respawned on the same loopback port with backoff (1/2/4/8/15/30 s, about a minute). The window origin stays the same, so the page's wire client reconnects its socket and resyncs threads on its own (src/wireClient.ts scheduleReconnect). The window title shows "Reconnecting…" meanwhile, and the remote page can no longer overwrite the title. A rejected token or a non-Solenta port stops retrying immediately. Giving up closes the window with the last SSH error, as before.

    Test: tunnel drop → respawn on the same port with the window kept open; unreachable host → 6 retries, then close plus a dialog showing the SSH stderr. A mutation check with retries disabled fails the test. Electron suite 4080/4081: the one failure is the unrelated load flake fetchCodex process lifecycle (missing pid file under load), offered as separate work.

    Remaining for #245: stable host identity (pinning which host a saved profile/token belongs to) and unattended host startup (launching --serve-web over SSH when nothing is listening).

  9. currentbits commented on Oct 3, 2026

    @currentbits
    OwnerAuthor

    Host identity slice pushed to draft PR #1408 (32723c37). SSH known_hosts is the pin: Connections now passes -o StrictHostKeyChecking=yes (command line overrides a relaxed ~/.ssh/config), so an unknown or changed host key refuses the tunnel before the web token is sent. Raw ssh stderr is replaced with instructions for the three cases BatchMode cannot prompt for: unknown host (run ssh host once in a terminal), changed key (verify and update known_hosts), and password-only login (key-based login required). Real messages were captured from OpenSSH and used in the tests. CI is green on the token (32014f94) and reconnect (aabec290) commits.

    Deliberately not built: a protocol-level host id or challenge-response. The SSH host key already identifies the machine. The remaining exposure is another local process on the remote host squatting on port 4620 and receiving the token, which is a multi-user-host concern. HMAC challenge-response in webBridge would close it if it ever matters.

    Left for #245: unattended host startup (launch --serve-web over SSH when nothing is listening). That needs a decision on what is installed remotely and how the token is minted and returned.

  10. currentbits commented on Oct 3, 2026

    @currentbits
    OwnerAuthor

    Final slice pushed to PR #1408 (844cda63), per the decisions on this thread: read the token over SSH, never auto-start.

    • A blank token reads userData/web-token on the host over SSH: ~/Library/Application Support/Solenta/web-token, then ${XDG_CONFIG_HOME:-~/.config}/Solenta/web-token, under sh -c so the login shell does not matter. Both release channels share productName "Solenta". The keychain copy is the fallback. SSH refusals surface before any tunnel is spawned.
    • When nothing listens on the remote port, ssh's open failed: connect failed now fails fast with "Nothing is listening on port N on host. Start Solenta there with --serve-web[=N]". Connect never launches the host.

    The PR description now covers the whole feature. Once #1408 merges, #245 is done as scoped. A Tailscale Serve zero-config path and a headless (non-Electron) host binary were never part of these slices; file them separately if wanted.

  11. added and removed on Oct 3, 2026
  12. currentbits commented on Oct 3, 2026

    @currentbits
    OwnerAuthor

    Shipped in #1408 (squash 99394430 on main). Settings → Connections opens a remote --serve-web host through SSH in an isolated window. A blank token is read from the host over SSH, with a keychain copy as fallback. Host keys are pinned (StrictHostKeyChecking=yes). Dropped tunnels reconnect behind a front port this process holds. The token only reaches ports ssh has bound. Nothing listening on the remote port fails fast; Connect never auto-starts the host, by decision. Verified by CI on macOS, Ubuntu and Windows, and end to end against real OpenSSH. Not in scope and not filed: Tailscale Serve zero-config, and a headless non-Electron host binary.

  13. added a commit that references this issue on Oct 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions