You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
feat(ports): forward a Remote Orca Server's ports through an SSH host used only for that - #33378
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
One flag on the existing link. The SSH access link (sshAccess) gains portsOnly in its schema, link request and pending intent.
The link verifies as today: a short tunnel proves this SSH host reaches the same paired server. Then the tunnel closes.
The environment keeps its preferred endpoint and gets no connectionDependency.
It also gets no pairing-revision bump. Request connections are keyed by that revision, so a bump would reconnect the server.
Runtime calls never dial SSH.getRuntimeSshAccess, the tunnel that carries a server's connection, skips ports-only access, so runtime calls, status checks and tunnel recovery don't touch SSH for it. A new getRuntimePortForwardSshTargetId names the host for the Ports panel and the server's Disconnect.
No Orca server over SSH. The host keeps the existing fence, so connecting it never deploys or converts anything. decideHostServer short-circuits a ports-only host, linked or still linking: it dials only the SSH transport and publishes the server's route. That route runs the existing restore of saved forwards.
Who reconnects the host. The link handler connects the host after a ports-only link. The server's Connect redials a host the user disconnected, in the background, so the server never waits on SSH.
Settings UI. The menu item, the dialog copy, the Ports via ‹host› line, and the chip shown only when SSH carries the 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.
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.
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
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.
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)
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.
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: 5
Check name
Status
Explanation
Title check
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
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
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
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
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.
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.
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.
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.
‼️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. */
…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>
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.
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>
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>
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.
…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>
@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.
@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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
⋯menu adds Forward ports through SSH…. You pick the SSH host and the server's port, then click Connect.curlor any other tool can use them.How this PR does it
sshAccess) gainsportsOnlyin its schema, link request and pending intent.connectionDependency.getRuntimeSshAccess, the tunnel that carries a server's connection, skips ports-only access, so runtime calls, status checks and tunnel recovery don't touch SSH for it. A newgetRuntimePortForwardSshTargetIdnames the host for the Ports panel and the server's Disconnect.decideHostServershort-circuits a ports-only host, linked or still linking: it dials only the SSH transport and publishes the server's route. That route runs the existing restore of saved forwards.sshAccess;getRuntimeSshAccess. A ports-only host restores through fix(ssh): saved port forwards come back after a restart that reconnects through the host's Orca server (#32484) #33388's served-status trigger instead;Why
Rationale
shell.openUrl). Today a paired server's port opens in a browser tab on the server (openWorkspacePortInBrowserinworkspace-port-actions.ts).portsOnlyand reads the link as ordinary SSH access: it still works, but the connection goes over SSH.Alternatives considered
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:
These behaviors already exist for SSH access links, and this PR leaves them alone.
Linked Issue
Closes #33156
Visual Proof
Before and after
On main the server already detects the dev server, but nothing forwards it to this machine:
This PR, step by step
Link an SSH host for ports only. The server keeps its address and shows Ports via ‹host›.
Forward the dev server to a local port (Ports tab → Forwarded → Add).
Open it in a browser on this Mac. The page is served on the container's loopback.
Drop the SSH session. The forward stops, and the server stays connected and answers.
Restart the app. The server reconnects directly and the saved forward comes back.
Stop using SSH. The forward ends, and the Ports tab leaves.
Demo setup
docker-dev-box, Linux arm64) running:orcad) from the released 1.4.237 app, paired directly over a published port, standing in for Tailscale;127.0.0.1:5173.fdcf887bf06.9b23bd6b6eb(which includes fix(ssh): saved port forwards come back after a restart that reconnects through the host's Orca server (#32484) #33388), I reran the whole after flow, with the same results.BrowserWindowon the Mac loading the forwarded URL. Opening the real system browser would take focus.fetchfrom the test process shows that any local tool reaches the forward.Verification log
ws://127.0.0.1:52481, noconnectionDependency;fetchof127.0.0.1:5173from the Mac:ECONNREFUSEDconnectionDependencynull,sshAccess.portsOnlytrue; SSH hostconnected200 demo-app on docker-dev-boxECONNREFUSED; server still answersrepo.list200(restored)ECONNREFUSED→200ECONNREFUSED→200; endpoint unchangedECONNREFUSED; server still answers; no Ports tab200(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.getRuntimeSshAccessexcludes 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 tcandpnpm buildpass. The changed-code quality gate passes. The localization checks pass; only the English catalog changes, perconfig/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:9b23bd6b6ebhere:pty-runtime-hidden-at-spawn-mark.test.tsandpty-pane-materialization-race.test.ts.preflight-provider-command-selection.test.tspass when run alone. I didn't rerun the fullpnpm 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).
Review
Agent skill upstream boundary
Notes
Ensure no issues in: Security, Cross-platoform support (Linux, Windows, Mac), Remote SSH, Mobile, general backwards compatibility, performance
Checklist
N/Awith reasonpnpm lint,pnpm typecheck,pnpm test, andpnpm buildpass (or CI will cover; local preferred)🤖 Generated with Claude Code