Skip to content

feat(ports): forward a Remote Orca Server's ports through an SSH host used only for that - #33378

Open
jbamlee wants to merge 8 commits into
stablyai:mainfrom
jbamlee:feat-remote-server-port-forwarding
Open

jbamlee wants to merge 8 commits into
stablyai:mainfrom
jbamlee:feat-remote-server-port-forwarding

Conversation

@jbamlee

@jbamlee jbamlee commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

ELI5

Proposal: give Remote Orca Servers the port forwarding that SSH hosts have, so a dev server on the server opens in your own browser.

Today a paired server's dev server (say on that machine's 127.0.0.1:5173) opens only in an Orca browser tab that runs on the server. Your Mac's browser and other local tools can't reach it. If that machine is also reachable over SSH, this lets you pick that SSH host only for forwarding. The server stays connected the way it is, and no Orca server is set up over SSH.

This is the second option in #33156, built as a working proposal. It isn't a bug fix, and it doesn't bring SSH back into how a Remote Orca Server connects. If this direction doesn't fit, or there's no response by 2026-11-08 (two weeks), I'll close this PR and #33156.

What Changed

Before and after

  • Before: a paired server's workspace has no Ports tab, though Orca detects the server's listeners (status bar). Open on a detected port creates a browser tab on the server. Nothing listens on this machine. Connect through SSH… moves the server connection itself onto SSH, and still shows no Ports tab.
  • After:
    • The server's ⋯ menu adds Forward ports through SSH…. You pick the SSH host and the server's port, then click Connect.
    • The row keeps the server's address and adds Ports via ‹host›, without the "SSH tunnel" chip.
    • The workspace gets the Ports tab with a Forwarded section. Forwards listen on this machine, and the system browser, curl or any other tool can use them.
    • Saved forwards come back after the SSH host reconnects, after the server's Disconnect and Connect, and after an app restart.
    • Stop using SSH ends them and hides the tab. Linking again restores the saved list.
  • Unchanged: servers without this link. Connect through SSH… still routes the connection through SSH, and now also gets the Ports tab (first three commits).

How this PR does it

Why

Rationale

  • It keeps the server connection off SSH. No Orca server is set up over SSH. SSH only carries this client's forwards, which is how a managed server's forwards already work (fix(ports): keep port forwarding controls for an SSH host that moved to its managed server #29427): the server scans its own ports, and the forwards ride the SSH connection.
  • It works in the system browser. The forwards listen on this machine, and the Forwarded section opens them with the system browser (shell.openUrl). Today a paired server's port opens in a browser tab on the server (openWorkspacePortInBrowser in workspace-port-actions.ts).
  • It reuses the existing link. Verification, crash-safe intents, the fence and unlink all come from the SSH access link that already exists, so there is no parallel mechanism.
  • It changes the client only. There is no RPC or protocol change, so mixed client and server versions are unaffected. The demo server is the released 1.4.237. An older client strips portsOnly and reads the link as ordinary SSH access: it still works, but the connection goes over SSH.

Alternatives considered

  • Forward over Orca's own connection (option 1 in [Feature]: Port forwarding for Remote Orca Servers #33156). It would cover servers without SSH, but it needs a new stream negotiated by capability. This PR doesn't rule it out.
  • A separate forward-only link type. It would duplicate the verification, intent journaling and fence that the SSH access link already has.
  • Add the machine under SSH Hosts. That sets up a second Orca server there.
  • Connect through SSH… It moves the server connection onto SSH.

Trade-offs and open choices

  • The link still asks for the server's port, to prove once that the SSH host reaches this server. Dropping that check and trusting the user's pick would be simpler, but forwards could then land on another machine.

  • The forwarding host is fenced to the server, so the SSH Hosts page treats it as it treats SSH access hosts today:

    • it shows the "Orca server" badge;
    • Remove is refused while it's linked;
    • editing its connection settings makes Stop using SSH fail until they're reverted.

    These behaviors already exist for SSH access links, and this PR leaves them alone.

Linked Issue

Closes #33156

Visual Proof

Before and after

main This PR
Workspace on the paired server main: no Ports tab This PR: Ports tab with a forward
Server menu main: server menu This PR: Forward ports through SSH

On main the server already detects the dev server, but nothing forwards it to this machine:

main: detected ports in the status bar

This PR, step by step

  1. Link an SSH host for ports only. The server keeps its address and shows Ports via ‹host›.

    Forward ports through SSH dialog Server row after linking
  2. Forward the dev server to a local port (Ports tab → Forwarded → Add).

    Forward a Port dialog
  3. Open it in a browser on this Mac. The page is served on the container's loopback.

    Forwarded page in a local browser
  4. Drop the SSH session. The forward stops, and the server stays connected and answers.

    Server connected while its SSH host is down
  5. Restart the app. The server reconnects directly and the saved forward comes back.

    Ports after relaunch
  6. Stop using SSH. The forward ends, and the Ports tab leaves.

    SSH route dialog No Ports tab after unlinking

Demo setup

  • Remote machine: a Docker container (docker-dev-box, Linux arm64) running:
    • sshd;
    • the Orca server (orcad) from the released 1.4.237 app, paired directly over a published port, standing in for Tailscale;
    • a dev server bound only to the container's 127.0.0.1:5173.
  • This Mac: e2e builds driven through the UI by a local Playwright script that isn't committed. Screenshots come from the hidden renderer over CDP.
  • Local browser stand-in: the "local browser" shot is a plain Electron BrowserWindow on the Mac loading the forwarded URL. Opening the real system browser would take focus. fetch from the test process shows that any local tool reaches the forward.
Verification log
Step Result
Paired directly preferred endpoint ws://127.0.0.1:52481, no connectionDependency; fetch of 127.0.0.1:5173 from the Mac: ECONNREFUSED
main: server menu Disconnect, Connect through SSH…, Check for updates, Details, Remove…; no Ports tab
Link ports-only endpoint unchanged, connectionDependency null, sshAccess.portsOnly true; SSH host connected
Ports tab → Add 5173 → local port 200 demo-app on docker-dev-box
SSH host disconnected forward ECONNREFUSED; server still answers repo.list
SSH host connected again forward 200 (restored)
Server Disconnect → Connect ECONNREFUSED → 200
Quit → relaunch ECONNREFUSED → 200; endpoint unchanged
Stop using SSH ECONNREFUSED; server still answers; no Ports tab
Link again 200 (saved forward restored)

Testing

  • I manually tested these changes locally

  • Automated tests added/updated, or explained why not below

  • Manual: the Docker demo above, on macOS (Apple silicon) against a Linux server, for both builds.

  • Automated, this change:

    • ssh-host-server-ports-only-connect.test.ts: a ports-only host, linked or still linking, dials only its SSH connection and never decides a server. A host whose SSH access carries the connection still does.
    • runtime-environment-ssh-access-store.test.ts: a ports-only link keeps the endpoints and the preferred endpoint, adds no dependency and no pairing-revision bump, and unlinks the same way. getRuntimeSshAccess excludes it, and the forward target includes it.
    • runtime-ssh-access.test.ts: a ports-only link verifies through a tunnel, then closes it without invalidating the transport. A retry changes nothing, a different mode is refused, and unlink ends the session without invalidating.
    • runtime-ssh-access-handlers.test.ts: a ports-only link connects its host; a normal link restores forwards instead.
    • runtime-environment-managed-host-disconnect.test.ts: the server's Disconnect closes the ports host. Connect redials it without waiting, and succeeds while the host is unreachable.
    • runtime-environment-managed-tunnel.test.ts: an SSH access tunnel coming up restores its host's saved forwards.
    • ports-panel-host.test.ts: the forward target of a ports-only server.
  • Automated, first three commits: their tests come from feat(ports): forward ports for a remote Orca server through its SSH access #32200.

  • Gates on 9b23bd6b6eb + this branch: pnpm lint, pnpm tc and pnpm build pass. The changed-code quality gate passes. The localization checks pass; only the English catalog changes, per config/i18n-translation-source.md.

  • Affected suites: src/main/ssh, src/main/ipc, src/main/managed-server, src/shared, and the renderer's settings, right-sidebar and lib folders. 37,194 tests pass. 4 fail:

    • 2 fail the same way on main 9b23bd6b6eb here: pty-runtime-hidden-at-spawn-mark.test.ts and pty-pane-materialization-race.test.ts.
    • 2 in preflight-provider-command-selection.test.ts pass when run alone. I didn't rerun the full pnpm test; in the earlier run for feat(ports): forward ports for a remote Orca server through its SSH access #32200, its failures on this machine were environment-related or also failed on main. So the last checklist box stays unchecked.
  • Platforms: macOS client, Linux server. There are no platform branches.

AI Disclosure

Implemented with Claude Code (Claude Opus 5.5). The issue draft and this diff were reviewed by Codex (gpt-6-astra, xhigh reasoning).

  • Each finding was checked against the source.
  • A pending ports-only link now takes the same connect path as a linked one.
  • The findings about the SSH Hosts page describe behavior SSH access links already have, so they are listed under Trade-offs above.

Review

Agent skill upstream boundary

  • Not applicable, or this change copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

Ensure no issues in: Security, Cross-platoform support (Linux, Windows, Mac), Remote SSH, Mobile, general backwards compatibility, performance

  • Security: forwards bind on this machine as SSH host forwards do today, and the link proves which server the host reaches.
  • Mobile: the mobile app has no port-forward UI, so there is nothing to mirror there.
  • Compatibility: there is no RPC, stream or protocol change. An older client reads a ports-only link as ordinary SSH access.
  • Cost: the connect path reads the environment list once for a fenced host.

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred)

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

Review in 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
Recent review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 9ac5a516-b27c-445f-b999-27275b83ec69
Commits

Reviewing files that changed from the base of the PR and between 270af5b and f28507f.

Files selected for processing: 2
  • src/main/ipc/ssh/ssh-host-server-connect.ts
  • src/main/ipc/ssh/ssh-host-server-ports-only-connect.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

Walkthrough

The changes add ports-only SSH access for paired servers. These links preserve the server’s existing connection route while supporting saved port forwarding through an SSH target. Link and unlink handling now manages SSH sessions and restores forwards. SSH routing and disconnect behavior recognize ports-only access. The settings UI provides a separate port-forwarding mode, and the Ports panel resolves SSH targets for paired servers.

Merge Risk: Moderate · previous review f2850

A failed link may restore hidden forwards, while interrupted reconnects can leave saved forwards unavailable. Resolve these lifecycle issues before merging.

Pre-merge checks: Passed 5
Check name Status Explanation
Title check Passed The title clearly identifies the main change: forwarding a Remote Orca Server's ports through an SSH host used only for forwarding. Its ending is grammatically incomplete, but the title remains specif…
Description check Passed The description is detailed and covers the user impact, implementation, rationale, alternatives, linked issue, visual proof, testing, compatibility, and AI disclosure. It omits the explicit Risk secti…
Linked Issues check Passed Active directly linked issue #33156 requires port forwarding for Remote Orca Servers: show and add forwards, open them locally or in the system browser, and restore saved forwards after reconnect. Clo…
Out of Scope Changes check Passed Changes map to the port-forwarding objective of #33156 and to the carried-over #32200 behavior the PR reuses:
- Schema and store (runtime-environments.ts, runtime-environment-ssh-access-store.ts, …
Docstring Coverage Passed Docstring coverage is inconclusive: files from earlier reviews still need to be rechecked within the check's analysis limits. Not blocking.
Full details: Title check

Explanation

The title clearly identifies the main change: forwarding a Remote Orca Server's ports through an SSH host used only for forwarding. Its ending is grammatically incomplete, but the title remains specific and directly related to the changeset.

Full details: Description check

Explanation

The description is detailed and covers the user impact, implementation, rationale, alternatives, linked issue, visual proof, testing, compatibility, and AI disclosure. It omits the explicit Risk section and leaves the full test gate unchecked, but it documents the relevant risks and test failures elsewhere and is mostly complete.

Full details: Linked Issues check

Explanation

Active directly linked issue #33156 requires port forwarding for Remote Orca Servers: show and add forwards, open them locally or in the system browser, and restore saved forwards after reconnect. Closed issue #32200 is historical context only and adds no active requirement.

Requirement mapping at head f28507f:

  • Listing and adding forwards: the server's workspace gets a Ports tab with a Forwarded section when sshAccess is present (ports-panel-host.ts, getRuntimePortForwardSshTargetId).
  • Local access: forwards listen on this machine, so the system browser and other local tools can reach them. The PR's demo log records a 200 response from the forward on the Mac.
  • Restore after reconnect: the link handler restores saved forwards for normal links (restoreManagedHostPortForwards). Ports-only links connect the SSH host (connectRegisteredSshTarget). The tunnel-up path restores forwards through getRuntimeSshAccess, and the server Disconnect/Connect path redials the host (ssh-user-disconnect). Tests cover these paths.
  • Option 2 scope: getRuntimeSshAccess excludes ports-only links, and decideHostServer (ssh-host-server-connect.ts) connects a ports-only host without resolving or deploying a server. The server connection keeps its direct endpoint. This matches the issue's exclusions: no server connection over SSH and no Orca server set up on the SSH host.

Changes since the previous review (ssh-host-server-connect.ts, runtime-ssh-access-handlers.ts, ssh-host-user-disconnect.ts, ssh-session-teardown.ts) add a pending-operation guard and an explicit ports-only connect path. They support the same requirements and do not remove any of them. The previous PASS conclusion remains supported.

No concrete unmet coding requirement was found. The PR also does not implement option 1 of #33156. The issue presents option 1 as an alternative, not a requirement.

Full details: Out of Scope Changes check

Explanation

Changes map to the port-forwarding objective of #33156 and to the carried-over #32200 behavior the PR reuses:

  • Schema and store (runtime-environments.ts, runtime-environment-ssh-access-store.ts, runtime-ssh-access.ts): add the portsOnly flag to the existing SSH access link. This supports the ports-only mode.
  • Link and unlink (runtime-ssh-access.ts, runtime-ssh-access-handlers.ts, ssh-target-orcad-claims.ts): verify the link, close the verification tunnel, and end the SSH session on unlink. allowSavedPortForwards lets linking proceed when saved forwards exist, which the restore requirement needs.
  • Connection handling (ssh-host-server-connect.ts, ssh-session-teardown.ts, ssh-host-user-disconnect.ts): a fenced ports-only host connects without server deployment. The new code fails only when a ports-only change is still pending, which is an explicit guard rather than an unrelated change.
  • Renderer (ports-panel-host.ts, runtime-server-row*.ts(x), RuntimeSshAccessControl.tsx, runtime-ssh-access-dialog.tsx, en.json): the Ports tab, the Forward ports through SSH menu item, the Ports via label, the dialog copy, and the English strings.
  • Tests: cover the new paths and do not expand scope.

The PR adds no RPC, stream, or persisted-format change beyond the optional portsOnly schema field. It does not change the Remote Orca Server connection to use SSH. It does not set up an Orca server over the SSH host. No change was found that is unrelated to port forwarding.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1


Review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: b3d39a9a-9f12-4b18-9ac2-3365b5c534a8
Commits

Reviewing files that changed from the base of the PR and between f392c6b and e30da3e4606669a5ec3ae783679c1c8f2949c222.

Files selected for processing: 28
  • src/main/ipc/runtime-environments/runtime-environment-connectivity-handlers.ts
  • src/main/ipc/runtime-environments/runtime-environment-managed-host-disconnect.test.ts
  • src/main/ipc/runtime-ssh-access-handlers.test.ts
  • src/main/ipc/runtime-ssh-access-handlers.ts
  • src/main/ipc/ssh/ssh-host-server-connect.ts
  • src/main/ipc/ssh/ssh-host-server-ports-only-connect.test.ts
  • src/main/ipc/ssh/ssh-host-user-disconnect.ts
  • src/main/ipc/ssh/ssh-managed-host-port-forward-lifecycle.test.ts
  • src/main/ipc/ssh/ssh-managed-port-forward-restore.ts
  • src/main/ipc/ssh/ssh-port-forward-restore.test.ts
  • src/main/ipc/ssh/ssh-session-teardown.ts
  • src/main/ssh/runtime-ssh-access.test.ts
  • src/main/ssh/runtime-ssh-access.ts
  • src/main/ssh/targets/ssh-target-orcad-claims.test.ts
  • src/main/ssh/targets/ssh-target-orcad-claims.ts
  • src/renderer/src/components/right-sidebar/ports-panel-host.test.ts
  • src/renderer/src/components/right-sidebar/ports-panel-host.ts
  • src/renderer/src/components/settings/RuntimeSshAccessControl.tsx
  • src/renderer/src/components/settings/runtime-server-row-menu.tsx
  • src/renderer/src/components/settings/runtime-server-row-meta.ts
  • src/renderer/src/components/settings/runtime-server-row.tsx
  • src/renderer/src/components/settings/runtime-ssh-access-dialog.tsx
  • src/renderer/src/i18n/locales/en.json
  • src/shared/runtime-environment-sidecar.ts
  • src/shared/runtime-environment-ssh-access-store.test.ts
  • src/shared/runtime-environment-ssh-access-store.ts
  • src/shared/runtime-environments.ts
  • src/shared/runtime-ssh-access.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

Comment on lines +9 to +17
/** A managed host, or one carrying a paired server's SSH access, restores forwards on connect. */
function restoresOnConnect(targetId: string): boolean {
return (
getSshHostServerStatus(targetId)?.kind === 'managed' ||
listEnvironments(getAppEnvironment().getPath('userData')).some(
(environment) => environment.sshAccess?.sshTargetId === targetId
)
)
}

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.

Minor · Stability & Availability · Quick win

🔎 Supported by static analysis

Script executed:

sed -n '1,110p' src/main/ipc/ssh/ssh-managed-port-forward-restore.ts
rg -n 'restoreManagedHostPortForwards|function listEnvironments|export .*listEnvironments' src/main src/shared

Repository: stablyai/orca

Length of output: 3674


Script executed:

sed -n '1,180p' src/shared/runtime-environment-store.ts
printf '\n--- app environment ---\n'
sed -n '1,180p' src/shared/app-environment.ts
printf '\n--- ssh host connect ---\n'
sed -n '1,170p' src/main/ipc/ssh/ssh-host-server-connect.ts
printf '\n--- state callbacks ---\n'
sed -n '1,110p' src/main/ipc/ssh/ssh-connection-state-callbacks.ts
printf '\n--- store usages and tests ---\n'
rg -n -F --glob '*.ts' -e 'listEnvironments(' -e 'runtime-environment-store' src/main src/shared | head -160
sed -n '1,210p' src/main/ipc/ssh/ssh-port-forward-restore.test.ts

Repository: stablyai/orca

Length of output: 41260


Script executed:

sed -n '1,180p' src/shared/runtime-environment-store.ts
sed -n '1,150p' src/main/ipc/ssh/ssh-host-server-connect.ts
sed -n '1,90p' src/main/ipc/ssh/ssh-connection-state-callbacks.ts
sed -n '1,190p' src/main/ipc/ssh/ssh-port-forward-restore.test.ts

Repository: stablyai/orca

Length of output: 21777


Script executed:

sed -n '1,260p' src/shared/runtime-environment-store-file.ts
printf '\n--- throwing store tests ---\n'
sed -n '105,145p' src/shared/runtime-environment-sidecar.test.ts
sed -n '235,265p' src/shared/wsl-host-deployment-store.test.ts
printf '\n--- SSH owner binding ---\n'
sed -n '1,180p' src/shared/managed-orcad-ssh-owner.ts
printf '\n--- decision callers ---\n'
rg -n -F --glob '*.ts' -e 'decideHostServer(' -e 'portsOnlyEnvironmentOf(' src/main src/shared

Repository: stablyai/orca

Length of output: 11659


Script executed:

sed -n '105,175p' src/main/ipc/ssh/ssh-connect-flow.ts
sed -n '1,90p' src/main/ipc/ssh/ssh-host-server-ports-only-connect.test.ts
sed -n '85,115p' src/main/ipc/ssh/ssh-host-server-connect.ts

Repository: stablyai/orca

Length of output: 5973


Handle eligibility-read failures before returning from the restore helper.

listEnvironments can throw when the environment sidecar is invalid. The initial eligibility read runs before the restore promise and its .catch. During a recovered connection with no managed-host status, handleSshConnectionStateChange can therefore let that exception escape and skip saved-forward restoration.

Suggested fix
 function restoresOnConnect(targetId: string): boolean {
-  return (
-    getSshHostServerStatus(targetId)?.kind === 'managed' ||
-    listEnvironments(getAppEnvironment().getPath('userData')).some(
-      (environment) => environment.sshAccess?.sshTargetId === targetId
-    )
-  )
+  if (getSshHostServerStatus(targetId)?.kind === 'managed') {
+    return true
+  }
+  try {
+    return listEnvironments(getAppEnvironment().getPath('userData')).some(
+      (environment) => environment.sshAccess?.sshTargetId === targetId
+    )
+  } catch (error) {
+    console.warn(`[ssh] Could not determine port-forward eligibility for ${targetId}:`, error)
+    return false
+  }
 }
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/** A managed host, or one carrying a paired server's SSH access, restores forwards on connect. */
function restoresOnConnect(targetId: string): boolean {
return (
getSshHostServerStatus(targetId)?.kind === 'managed' ||
listEnvironments(getAppEnvironment().getPath('userData')).some(
(environment) => environment.sshAccess?.sshTargetId === targetId
)
)
}
/** A managed host, or one carrying a paired server's SSH access, restores forwards on connect. */
function restoresOnConnect(targetId: string): boolean {
if (getSshHostServerStatus(targetId)?.kind === 'managed') {
return true
}
try {
return listEnvironments(getAppEnvironment().getPath('userData')).some(
(environment) => environment.sshAccess?.sshTargetId === targetId
)
} catch (error) {
console.warn(`[ssh] Could not determine port-forward eligibility for ${targetId}:`, error)
return false
}
}

jbamlee and others added 4 commits November 1, 2026 02:37
…ed over SSH

A paired server whose "Connect through SSH" link names an SSH host gets the
Ports tab, and its forwards ride that host's connection, as a managed server's
do since stablyai#29427. Its tunnel coming up restores the host's saved forwards, as a
managed server's tunnel does since stablyai#32484, so saved forwards no longer block
linking SSH access.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…Disconnect

- The SSH access claim lets saved forwards through too, not only the link
  check, so a host with saved forwards links instead of failing at claim.
- The link handler restores saved forwards once the link is saved; the
  link's own connect came before it.
- A paired server's Disconnect also disconnects the SSH host it is linked
  through, as a managed server's does, which closes its forwards.
- Unlink closes the host's live forwards after the unlink commits and keeps
  their saved definitions for a relink.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rwards

Closing only the forwards after the unlink committed left three gaps: a
restore already running could add one after the sweep, a managed verdict
from an earlier explicit connect could restore them on a later reconnect,
and an unlink that failed after hiding the Ports tab never closed them.

Unlink now ends the host's SSH session right after closing its tunnel,
inside the lifecycle queue it already holds, the way Disconnect does: the
forwards close, the server verdict is forgotten, and a restore in flight
drops what it adds once the connection is gone. Saved forwards stay for a
relink.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… used only for that

A paired server reached directly (for example over Tailscale) had no port
forwarding, even when its machine is reachable over SSH. Linking it with
"Connect through SSH…" brought the Ports tab, but also moved the server
connection onto SSH.

"Forward ports through SSH…" links the SSH host with portsOnly. The link still
proves through a short tunnel that the host reaches this server, then closes
it. The server keeps its endpoint, has no SSH connection dependency, and no
pairing revision bump. The host keeps the fence, so connecting it never sets
up an Orca server: the connect dials only the SSH transport and publishes the
server's route, which restores saved forwards. The Ports tab and the server's
Disconnect use the host; the server's Connect redials it in the background so
the server never waits on SSH. Older builds read the link as plain SSH access.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jbamlee
jbamlee force-pushed the feat-remote-server-port-forwarding branch from c691e6b to 00057cc Compare October 31, 2026 18:14
@jbamlee

jbamlee commented Oct 31, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto current main (43655478997). The conflicts came from recent comment cleanups and from #36407 removing ssh-managed-host-port-forward-lifecycle.test.ts. I dropped the one case this branch had added to that file; the unlink behavior is still covered in runtime-ssh-access.test.ts. Typecheck, lint, and the SSH, runtime environment, Ports and Settings suites pass locally (6,785 tests).

As noted above, I'll close this and #33156 if there's no response by 2026-11-08. Happy to change the approach before then.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1


Review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: a8258605-b256-439c-a21c-e8d994cfd632
Commits

Reviewing files that changed from the base of the PR and between c691e6b and 00057cc.

Files selected for processing: 16
  • src/main/ipc/runtime-environments/runtime-environment-connectivity-handlers.ts
  • src/main/ipc/runtime-environments/runtime-environment-managed-tunnel.test.ts
  • src/main/ipc/runtime-environments/runtime-environment-managed-tunnel.ts
  • src/main/ipc/runtime-ssh-access-handlers.ts
  • src/main/ipc/ssh/ssh-host-server-connect.ts
  • src/main/ipc/ssh/ssh-host-user-disconnect.ts
  • src/main/ssh/runtime-ssh-access.ts
  • src/main/ssh/targets/ssh-target-orcad-claims.ts
  • src/renderer/src/components/right-sidebar/ports-panel-host.test.ts
  • src/renderer/src/components/right-sidebar/ports-panel-host.ts
  • src/renderer/src/components/settings/RuntimeSshAccessControl.tsx
  • src/renderer/src/components/settings/runtime-server-row-menu.tsx
  • src/renderer/src/components/settings/runtime-server-row-meta.ts
  • src/renderer/src/i18n/locales/en.json
  • src/shared/runtime-environment-sidecar.ts
  • src/shared/runtime-environments.ts
Files skipped from review as they are similar to previous changes: 1
  • src/renderer/src/i18n/locales/en.json

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 8 remain after this review.

… unfinished

A ports-only "Stop using SSH" that failed or was cut short by a quit
leaves its unlink pending: the Ports tab is gone, but the host still
routed as the paired server's managed host on connect, so its saved
forwards restored where nothing shows them. The connect now fails with
a message pointing at the unfinished stop. Routing the host into the
managed-server decision instead would open a managed tunnel for a
server with no deployment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment •

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.


Review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: b06682a8-2f87-4029-9b5e-9f0448831eff
Commits

Reviewing files that changed from the base of the PR and between 00057cc and 270af5b.

Files selected for processing: 2
  • src/main/ipc/ssh/ssh-host-server-connect.ts
  • src/main/ipc/ssh/ssh-host-server-ports-only-connect.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

…nfinished

A stuck ports-only link still routed a host connect as served, so saved
forwards came back with no Ports tab to show them. An unlink that stopped
after releasing the host's fence slipped past the guard, and a normal
connect could then set a server up on the host, leaving Retry unable to
finish the stop. Both now refuse the connect while Retry can still finish
the change on that host.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment •

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.

Follow-up Comments

Outside diff Preserve passive reconnect intent for ports-only SSH access.

107-111: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Preserve passive reconnect intent for ports-only SSH access.

When a ports-only SSH target has no SSH PTY and remains reconnecting during shutdown, activeConnectionIdsAtShutdown omits it. Startup then has no target to reconnect. If the paired server is connected later, liftSshUserDisconnectForEnvironment does not call connectRegisteredSshTarget because no user-disconnect marker exists. The saved forwards can remain unavailable.

Retain launch-enabled, passively reconnecting ports-only targets in the shutdown restore set. Keep explicit reconnectOnLaunch === false targets on the existing offline path.

Open the source location

jbamlee and others added 2 commits November 2, 2026 20:14
…st's pending stop

Another server's unfinished ordinary link can name the same released host.
The lookup took the first pending change for that host, so an ordinary one
listed first hid the ports-only stop and let the connect through.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e next launch

Launch redials a host that was still reconnecting at quit only when a
terminal session needs it, so a host that carries only a paired server's
port forwards stayed offline and its forwards never came back. A ports-only
access's forwards now keep its host on that list the same way. A host left
disconnected or failing sign-in stays offline, and the launch opt-out still
applies.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jbamlee

jbamlee commented Nov 2, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai About your follow-up on ssh-host-user-disconnect.ts:107-111: fixed in 28755eb. The launch list now keeps a ports-only SSH access's host while it is still reconnecting at quit, the same way a surviving terminal session keeps its host. Both builders do this: buildActiveConnectionIdsAtShutdown in the renderer and shutdown-reconnect-facts.ts in main. A host that is disconnected or failing sign-in at quit stays offline, and startup still skips targets with reconnectOnLaunch === false.

@coderabbitai

coderabbitai Bot commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

Analysis chain

Script executed:

#!/bin/bash
git show --stat --oneline 28755eb0d4c
fd -i '^(ssh-host-user-disconnect|shutdown-reconnect-facts)\.(ts|test\.ts)$'
rg -n -C3 'function buildActiveConnectionIdsAtShutdown|export .*buildActiveConnectionIdsAtShutdown|reconnectOnLaunch === false' --type ts --type tsx .

Length of output: 1487


Script executed:

#!/bin/bash
ast-grep outline src/main/ipc/ssh/ssh-host-user-disconnect.ts
ast-grep outline src/main/runtime/terminal/recovery/shutdown-reconnect-facts.ts
ast-grep outline src/renderer/src/lib/workspace-session-reconnect-targets.ts
git show --format=short 28755eb0d4c -- src/main/runtime/terminal/recovery/shutdown-reconnect-facts.ts src/renderer/src/lib/workspace-session-reconnect-targets.ts
sed -n '1,190p' src/main/ipc/ssh/ssh-host-user-disconnect.ts
sed -n '1,220p' src/main/runtime/terminal/recovery/shutdown-reconnect-facts.ts
sed -n '1,240p' src/renderer/src/lib/workspace-session-reconnect-targets.ts
git show --format= 28755eb0d4c -- '*window-save-reconnect-facts.test.ts' '*workspace-session-reconnect-targets.test.ts'
rg -n -C4 'reconnectOnLaunch\s*===\s*false' -g '*.ts' -g '*.tsx' .

Length of output: 26784


Script executed:

#!/bin/bash
git show --format= 28755eb0d4c -- src/main/runtime/terminal/restored-terminal-placement.ts src/renderer/src/lib/workspace-session.ts src/renderer/src/lib/workspace-session-patch.ts
rg -n -C5 'portsOnlySshTargetIds|reconnectOnLaunch\s*!==\s*false|reconnectOnLaunch\s*==\s*false' -g '*.ts' -g '*.tsx' src

Length of output: 10508


@jbamlee Thanks. I inspected 28755eb and the current code. This addresses my follow-up.

  • Both shutdown builders retain a ports-only SSH host while it is reconnecting, even without a surviving terminal session.
  • Both builders exclude hosts with disconnected, auth-failed, or unobserved status.
  • Startup still excludes targets with reconnectOnLaunch === false.

The added regression tests cover reconnecting and disconnected ports-only hosts in both builders. I inspected the tests but did not run them.


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

You are interacting with an AI system.

This branch has not been deployed

No deployments
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.

[Feature]: Port forwarding for Remote Orca Servers

1 participant