Repository navigation
Description
Before submitting
- I searched existing issues and did not find a duplicate.
- I included enough detail to reproduce or investigate the problem.
Area
apps/mobile, apps/web, apps/desktop — connection catalog / pairing (packages/client-runtime/src/connection/*, mobile environment registry)
Summary
Clients store a raw IP:port endpoint as the environment's identity and never refresh it. When the desktop backend's port legitimately changes (its port is re-scanned at every launch, cf. #13747), every remotely paired device that saved host:port times out forever; the server cannot help because it never learns about the clients, and the clients never learn the server moved. The tailscale-serve URL is the one port-stable route, but it is not what pairing saves for mobile, so a port change silently bricks the phone app (10 000 ms connect timeout) until the user re-pairs by hand.
Ports shifting is #13747's half. This issue is the other half: saved endpoints should be refreshed from the server, and portable/serve routes should be preferred over raw host:port. Repaired by hand today; nothing repairs it automatically.
Reproduction (verified on 0.0.45, 2026-10-13)
- Desktop (Linux) runs its backend on the port it scanned at launch (was 4773 since Oct 11). A phone is paired/saved with a raw endpoint pinned to that host:port (mobile app also holds the tailscale name in its environment list, but dials the saved direct address).
- The desktop app is relaunched (Oct 13 07:46 UTC). Port scan picks the next free port — 4774. The app's own tailscale-serve integration re-points the serve URL to the new port (this part self-heals, and I verified
https://<server>.ts.net→ 200 OK instantly afterwards). - Nothing on the phone's side changes: its saved route still says the old port. Every connect attempt reaches a dead port → "Times out after 10000ms" in the mobile app, indefinitely. Server-side trace shows zero attempts from the phone reach the backend.
- Recovery requires manually editing the phone's saved endpoint or re-pairing. The old port stays dead (no listener), so there is not even an upgrade path.
Same class on other transports: #12641 / #19247 (T3 Connect stores the link-time origin and keeps forwarding to a dead port after restarts), #15751 (stale endpoint bookkeeping in the Connections UI), #13747 (why the port moves at all). They share one root property: a transport-specific address was persisted as durable identity.
Why the current model can't self-heal
ExecutionEnvironmentDescriptor(packages/contracts/src/environment.ts) is fetched only after a route works; it describes capabilities but carries no live endpoint information, so a connected client cannot learn the server's current address.- The connection catalog persists routes (
apps/…/connection/registry.ts,withRoutes/replaceRoutesLocked) but nothing on the server ever instructs a client to replace a dead route for the sameenvironmentId— and the client that can't connect can't be reached. - Mobile saves the direct URL (
endpointUrl) the user typed or LAN-scanned at pairing time; the port-stable tailscale URL exists (server side:tailscaleServeEnabled, serve re-pointed at every startup — verified) but is not what mobile ends up dialing first.
Proposed fixes
Ranked by value/risk; 1+2 together make "port changed" a non-event on every client, including already-paired stale ones.
1. Publish live endpoints in the descriptor (server, small).
Extend ExecutionEnvironmentDescriptor with the server's known routes, e.g.
"endpoints": {
"httpUrl": "http://192.168.1.10:4774", // current LAN direct endpoint
"tailscaleUrl": "https://archlinux…ts.net" // present when tailscale serve is enabled
}The server already recomputes exposure at startup (serverExposure.configureFromSettings({ port })), so this is pure plumbing at the descriptor site that all clients fetch on every connect.
2. Refresh saved routes from the descriptor (client, medium).
On a successful handshake, if descriptor.endpoints disagrees with the saved routes for that environmentId, upsert the new route / drop the dead one (the registry's route replace machinery already exists in client-runtime/src/connection/registry.ts). This keeps routing data live while at least one route works — no pairing-token changes needed.
3. Prefer the serve URL when creating environments (client, small).
When tailscaleServeEnabled/tailscale-exposed, save https://<server>.ts.net as the primary route (port changes are absorbed by serve's re-point) and keep the direct host:port as a secondary. Pure pairing-flow change; does not affect users without tailscale.
4. Discovery once all routes are dead (optional / resilience).
Stale routes that can't reach the server can't be fixed by the server. Options: advertise the serve URL in the pairing deep link (t3code:// scheme already exists for auth handoffs) so every client has at least one port-stable route from day one, or — the UX pattern that solves mobile cleanly — QR pairing on mobile (scan from desktop settings → pair by environmentId + device credential, endpoints become a cache rather than identity).
Impact
Major degradation or frequent failure. From the maker side this is the most-visible failure in the product for multi-device users ("I updated, now my phone/mac can't connect"), often mistaken for an update regression (which is exactly how it presented for us). Every fix at the server half (#13747) reduces how often it triggers, but until the client half exists, any real port change (install switch, port conflict, orphan reclaim, tunnel origin) leaves paired devices permanently dead.
Version or commit
Verified against upstream main@519d64f6ce (2026-10-14). Mobile app 0.0.46-nightly (Android), desktop 0.0.45 on Arch Linux.
Related
- [Bug]: Desktop backend outlives a crashed main process and keeps its port, so the relaunch silently moves to another port #13747 — port movement itself (accepted): orphan backend holds the port, relaunch scans away. This issue's fixes make that movement survivable.
- [Bug]: Desktop-linked environment never re-registers its port after restart; stale relay ingress when 3773 is taken #12641 / [Bug]: T3 Connect tunnel keeps forwarding to the port recorded at link time after the server's port changes #19247 — T3 Connect keeps the link-time origin after port changes (same persistence-model gap on the relay).
- [Bug]: Connections keeps stale network endpoints after reopening settings #15751 — stale network endpoints in the Connections UI.
- Thread model/provider selection is not authoritative: queued runs, cron, delegated completions, and stale clients freeze or durably revert it #19376 — thread model/provider selection is not authoritative (separate but same theme: client-cached value treated as durable identity).
Activity
juliusmarminge commentedon Oct 22, 2026
Tested Linux web in Chromium against current main b5a226e49c, with isolated server state:
permission-denied, so this did not test your case where a working HTTPS Serve URL should survive the move. The Android 10-second timeout was not verified. The existing endpoint-advertisement/route-learning tests passed: 3 files, 56 tests.The current shared client already learns advertised LAN/tailnet routes after a successful connection. Please retry with current host and mobile builds and provide the saved route list before and after a backend port change, plus the host log around startup/Serve configuration. Does the phone have an
https://…ts.netroute before the change, or only addresses with the old backend port? Omit pairing tokens and credentials. That distinguishes failure to learn a working Serve route from recovery after every saved route is unreachable.github-actions commentedon Oct 29, 2026
Closing: no reply in 7 days after we asked for more information. Comment with the missing details and we will reopen.