Skip to content

fix(editors): open remote projects over Tailscale SSH - #13636

Closed
vedprakash2302 wants to merge 2 commits into
pingdotgg:mainfrom
vedprakash2302:fix/remote-open-tailscale-ssh
Closed

vedprakash2302 wants to merge 2 commits into
pingdotgg:mainfrom
vedprakash2302:fix/remote-open-tailscale-ssh

Conversation

@vedprakash2302

@vedprakash2302 vedprakash2302 commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

What Changed

For a remote environment, Open in the chat header hands the local editor an SSH deep link to the host. Hosts that serve SSH only through Tailscale SSH now get that link.

  • When no sshd answers on loopback port 22, the server runs tailscale get --json ssh, which Tailscale added in 1.102. If Tailscale SSH is on, the server advertises the host's MagicDNS name as a new tailscale-ssh target, with the account T3 Code runs as. It reads this on every config load, as the sshd path reads tailscale status. Windows hosts skip the check because Tailscale SSH has no Windows server.
  • RemoteOpenTarget gets the tailscale-ssh kind and an optional user, limited to portable POSIX account names. buildRemoteOpenUrl puts the login before the host, for example zed://ssh/user@host/path.
  • The desktop's Zed link check accepts that validated user@, the form vscode-remote links already pass. Zed links with a password, a port, or a login that starts with - are still refused.
  • The picker passes the login through. For these hosts, its one-time hint says "Opens over Tailscale SSH. Needs tailnet access to <environment>" instead of asking for an SSH key.
  • docs/user/remote-access.md gets a short section on opening remote projects in an editor.

Hosts with sshd are unchanged. They advertise the same targets from the same single tailscale status call. Older clients, including the App Store build, drop the unknown kind through ForwardCompatibleArray and keep showing "No SSH route". The mobile app has no remote open.

Why

The server advertises hosts only after an sshd answers on its own loopback port 22. Tailscale SSH never answers there because it serves only tailnet peers. A host that runs Tailscale SSH instead of OpenSSH therefore shows "No SSH route", although VS Code and Zed can connect to it.

Once a link exists, the login decides whether it works. Tailscale SSH signs in as whichever account the client names, and editors send the local account name. From a Windows laptop that is DOMAIN\user, which the tailnet policy rejects. The server's own account owns the project, so the link names it.

UI Changes

For a Tailscale SSH host, the Open menu used to show a disabled "No SSH route to <environment>" item. It now lists the editors, with the Tailscale SSH hint on first use. I haven't attached screenshots of the menu yet. The verification below used real editor windows.

Verification

  • Focused tests cover the resolver, the tailscale get reader, decoding new and malformed targets, the desktop link check, and the link builder. The resolver tests check that sshd output and its single CLI call stay the same, that Windows starts no CLI, that a config load cut off mid-discovery doesn't affect the next one, and that logins a link can't carry are left out. Typecheck, lint, and format pass for the six touched packages.
  • On a WSL2 host whose only SSH server is Tailscale SSH 1.102.4, the resolver returned [{"kind":"tailscale-ssh","host":"<machine>.<tailnet>.ts.net","user":"<account>"}] in about 0.3 s.
  • The test laptop ran Windows with no User entry in its SSH config, and it reached the tailnet through a ProxyCommand into WSL's Tailscale. There the current link failed with tailnet policy does not permit you to SSH as user "DOMAIN\\user". The new vscode://vscode-remote/ssh-remote+<account>@<host>/… and zed://ssh/<account>@<host>/… links opened the project in VS Code 1.139 with Remote-SSH 0.128, and in Zed 1.21.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes (not yet, see UI Changes)
  • I included a video for animation/interaction changes (not applicable)

Model: Claude Opus 5.5; harness: OpenCode.

Summary by CodeRabbit

  • New Features
    • Remote projects can now be opened over Tailscale SSH when the host has no local SSH server and Tailscale SSH is enabled, where supported. Available targets include the host’s login name when it can be identified.
    • VS Code and Zed links now include the remote login when provided. The remote-links menu distinguishes Tailscale SSH access requirements from SSH key requirements.
  • Documentation
    • Added guidance for opening remote projects in VS Code, Cursor, VSCodium, and Zed, including SSH setup and Tailscale access requirements.

The server advertised SSH hosts for remote open links only after an sshd
answered on its loopback port 22. Tailscale SSH serves only tailnet peers,
so hosts that use it instead of OpenSSH showed "No SSH route" even though
VS Code and Zed can connect to them.

When no sshd answers, the server now runs `tailscale get --json ssh`. If
Tailscale SSH is on, it advertises the MagicDNS name as a `tailscale-ssh`
target with the account T3 Code runs as, and links name that account
before the host. Tailscale SSH signs in as whichever account the client
names, and editors otherwise send the local one. The desktop's Zed link
check accepts that login the way vscode-remote links already do.

Hosts with sshd advertise exactly what they did before. Older clients drop
the new target kind and keep showing "No SSH route".
@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Sep 25, 2026
@macroscopeapp

macroscopeapp Bot commented Sep 25, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR introduces automatic Tailscale SSH discovery and new user-qualified editor deep links across server, web, and desktop code. Because it changes remote-access authentication behavior and a desktop URL security boundary, the cross-cutting capability warrants human review.

You can add or adjust custom eligibility rules. Learn more.

@coderabbitai

coderabbitai Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: pingdotgg/t3code/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 83222510-13f5-4f44-bee4-8c2540f12638

📥 Commits

Reviewing files that changed from the base of the PR and between 5d98ec8 and 84a55b6.

📒 Files selected for processing (2)
  • apps/server/src/environment/RemoteOpenTargets.test.ts
  • apps/server/src/environment/RemoteOpenTargets.ts

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


📝 Walkthrough

Walkthrough

Remote-open targets now support Tailscale SSH when local sshd is unavailable. The flow can include a validated local username in VS Code and Zed links. The remote-open picker and documentation distinguish Tailscale SSH from OpenSSH routes.

Changes

Tailscale SSH remote-open flow

Layer / File(s) Summary
Remote-open target and login contract
packages/contracts/src/editor.ts, packages/contracts/src/server.ts, packages/contracts/src/server.test.ts, packages/shared/src/hostProcess.ts
The target contract adds the tailscale-ssh kind and an optional validated user. URL construction encodes the optional login, and host process context exposes the local username.
Tailscale SSH setting reader
packages/tailscale/src/tailscale.ts, packages/tailscale/src/tailscale.test.ts
The shared read-only command runner supports get. The SSH-setting reader decodes tailscale get --json ssh and reports command and parse failures.
Tailscale SSH target discovery
apps/server/src/environment/RemoteOpenTargets.ts, apps/server/src/environment/RemoteOpenTargets.test.ts
When local sshd is unavailable, target resolution checks Tailscale SSH and MagicDNS on each resolution. It skips Windows and includes a valid process username when available. Tests cover eligibility, interruption, username filtering, and existing sshd targets.
Editor links and route guidance
apps/web/src/remoteOpen.ts, apps/web/src/remoteOpen.test.ts, apps/web/src/components/chat/OpenInPicker.tsx, apps/desktop/src/electron/ElectronShell.ts, apps/desktop/src/electron/ElectronShell.test.ts, docs/user/remote-access.md
The web flow preserves the optional user in VS Code and Zed links. Electron accepts valid Zed user@host paths and rejects malformed login forms. The picker and documentation describe route-specific requirements.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant RemoteOpenTargets
  participant TailscaleCLI
  participant OpenInPicker
  participant buildRemoteOpenUrl
  participant ElectronShell
  RemoteOpenTargets->>TailscaleCLI: Read SSH setting and MagicDNS status
  TailscaleCLI-->>RemoteOpenTargets: Return setting and status
  RemoteOpenTargets-->>OpenInPicker: Provide SSH target and optional user
  OpenInPicker->>buildRemoteOpenUrl: Build editor URL with target and user
  buildRemoteOpenUrl-->>OpenInPicker: Return VS Code or Zed URL
  OpenInPicker->>ElectronShell: Open editor URL
  ElectronShell-->>OpenInPicker: Accept or reject URL
Loading

Merge Risk: ⚪ Minimal · up to 84a55

No actionable issue is established in the Tailscale SSH remote-open change; it is mergeable after normal checks.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 84a55

The new editor links retain account-name validation and rely on tailnet access and SSH policy. Repeated route discovery may add host load, but no authorization bypass was established.

Retained concerns

  • Low · reliability · inferred: Repeated authorized configuration requests can each start fresh Tailscale probes on hosts without local sshd. The per-request timeout limits waiting, but does not itself limit aggregate process starts or route loss under load.
Security review details

Security Blast Radius

  • inferred — The new route can direct an editor to the server host under the advertised account, subject to tailnet reachability and the host’s SSH policy. The reviewed change does not itself grant that access.

Security Findings and Attack Paths

  • inferred — The changed public-entrypoint ranges are test fixtures, not a new production entrypoint. The production configuration handlers use authorization checks, and the reviewed login and desktop-link controls do not establish a new credential or command-injection path.

Trust Boundaries and Controls

  • observed — The server derives the advertised login from its host process and validates it before publishing the target. Configuration RPCs check session scope; the desktop independently checks editor-link structure and rejects URL-level credentials.

Resilience and Maintainability Implications

  • inferred — Command failure and timeout degrade route discovery to no target, but the reviewed timeout does not demonstrate a limit on aggregate Tailscale processes from concurrent authorized configuration requests.

Hardening Proposals

  • proposed — Consider coalescing concurrent route probes or bounding their aggregate frequency if configuration-request volume can be high.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 13 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 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.
Title check ✅ Passed The title clearly and concisely describes the main change: enabling remote project opening over Tailscale SSH.
Description check ✅ Passed The description includes the required What Changed, Why, UI Changes, and Checklist sections. It explains the implementation, rationale, UI impact, and verification. Screenshots are not attached, but t…
  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR

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

The one-minute cache kept whatever exit its first caller produced. When a
client disconnected during discovery, the cache kept that interruption, so
on hosts without sshd every config load for the next minute failed, and so
did loads already waiting on the same discovery.

Discovery now runs on every config load, as the sshd path already runs
`tailscale status`. Clients load config when they connect, so the cache
rarely saved a read.

Copy link
Copy Markdown
Member

Note

This comment is posted by Julius' dot

Closing for missing UI verification. The real editor checks are useful, but the PR changes the Open menu from “No SSH route” to editor choices with a new Tailscale hint and explicitly says those screenshots are not attached. Please add before/after screenshots of that affected menu, then request reconsideration.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L 100-499 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants