Skip to content

fix(web): Open in VS Code uses the LAN address the client reached - #21

Merged
tebiggs merged 2 commits into
mainfrom
feature/remote-open-connection-host
Oct 30, 2026
Merged

tebiggs merged 2 commits into
mainfrom
feature/remote-open-connection-host

Conversation

@tebiggs

@tebiggs tebiggs commented Oct 30, 2026

Copy link
Copy Markdown
Owner

Problem

A client that reaches a remote server over the LAN gets an Open in VS Code link to the server's tailnet name when the server has one, even if the client is not on the tailnet. On the reporting setup, a desktop connected by LAN address and not on the tailnet ended up in an empty VS Code window in Restricted Mode.

The server side of this is confirmed: the client's connection came over the LAN, the client has no tailnet entry, and sshd listens on all interfaces with Tailscale SSH off. Nothing on the client machine was inspected, including the VS Code error and whether Remote-SSH is installed. That the failed connection causes Restricted Mode is an inference.

Fix

When sshd is listening and the client is connected over a direct, non-public route, the link uses that route's host with the advertised username, for example dev@192.168.1.50. An SSH profile alias still wins.

  • sshd signal: the server marks sshd-backed targets with sshd: true instead of the client inferring it from the mdns target. A Tailscale-SSH-only server has no marker and keeps its tailnet name. Older clients ignore the field, and a client talking to an older server keeps the old link.
  • Which route: the route the client is connected on, not the first saved one. A saved LAN route can outrank the live tailnet route on a client that has left that LAN.
  • Hosts that qualify: route kinds lan, tailnet and vpn from connectionRouteKind. That covers private IPv4, .local, bare hostnames, MagicDNS names and 100.64.0.0/10 addresses. Tailscale qualifies for the same reason as the LAN: the client has already reached the server at that host. A MagicDNS connection gets the same link as before. A connection to a bare 100.x address now gets that address instead of the MagicDNS name.
  • Hosts that do not qualify: public hostnames (Cloudflare tunnels, T3 Connect, reverse proxies), relay, SSH-forwarded and loopback routes, plus .localhost and 0.x addresses, which name the client itself. These keep today's behaviour.
  • IPv6 literals, including ULA, are left out. How the editors handle them in these links is untested, so they keep today's link.
  • ChatMarkdown.tsx only checks mode !== "local-exec" and passes no targets, so it is unaffected and unchanged.

Known limits

  • A private address that forwards only the T3 port (a reverse proxy, or a Docker-published port) gets a link to a host with no SSH behind it. An SSH profile alias is the way out.
  • The sshd check probes port 22 on loopback, as it did before, so an sshd bound only to loopback is reported as listening.

Rollout

The web code is bundled in the desktop app, so the desktop needs a release with this change. The server must also be updated to send sshd. Until both are updated, the link stays as it is today.

Verification

  • Live: ran this branch's dev server and opened it in headless Chromium through a forward on the host's LAN address. Clicking Open in VS Code requested vscode://vscode-remote/ssh-remote+<user>%40<lan-address>/<path>, using the host's real sshd and Tailscale state. That was a browser, not the desktop app, and VS Code itself was not exercised.
  • Focused tests in remoteOpen.test.ts and RemoteOpenTargets.test.ts, typecheck for contracts, web and server, plus lint and format, pass.
  • vp run check:changed could not run: git diff segfaulted in the sandbox before any check started.

🤖 Generated with Claude Code

tebiggs and others added 2 commits October 30, 2026 16:20
A client on the LAN got a link to the server's tailnet name, which it may
not be able to resolve. When sshd is listening and the connected route is
direct and non-public, the editor link now uses that route's host with the
advertised username. An SSH profile alias still wins.

The server now marks sshd-backed targets explicitly, so a Tailscale-SSH-only
host keeps its tailnet name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Also moves the test fixture to a neutral LAN address.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@tebiggs
tebiggs merged commit 4d0dc43 into main Oct 30, 2026
12 checks passed
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.

1 participant