Repository navigation
Package-installed desktop app and npm-installed server do not know about each other: stale backend keeps Tailscale Serve and a duplicate relay tunnel, blocking remote clients #22235
Description
What happened
The android phone's T3 Code app (T3 Connect) listed the WSL Arch machine ("server") but could never connect to it, while two other machines paired with the same account connected fine over both LAN and Tailscale. The phone was on the same tailnet and reachable in 8ms, yet no connection attempt ever reached the current T3 server.
Diagnosis
The machine had two T3 Code installs from two channels that don't know about each other, and this is the root of the whole mess:
- the desktop app, installed from the system package channel (AUR
t3code-nightly-bin) — on Arch this only moves when you explicitly upgrade packages, so it sat at a build from weeks earlier; - the server, installed via the npm/npx channel and run as a boot service — it self-updates silently on the nightly channel.
They share one data directory and one environment ID, but nothing coordinates them. The service crossed a protocol boundary automatically (orchestration protocol v2) while the desktop app stayed at its installed version (protocol v1). After that:
| Package-installed desktop app | npm/npx boot-service server | |
|---|---|---|
| Version | 0.0.43-nightly.20260923.2135 | 0.1.1-nightly.20261020.3386 |
| Listen | 0.0.0.0:3774 |
127.0.0.1:3773 (loopback only) |
| Orchestration protocol | v1 | v2 |
| T3 Connect relay tunnel | yes (own cloudflared, running for days) | yes |
Every remote route dead-ended at the stale backend:
- Tailscale Serve had been configured once (by the desktop app's Connections toggle) to proxy the tailnet HTTPS endpoint to the desktop backend's own port — and nothing ever re-pointed it.
- Direct tailnet/LAN IP: only the stale backend bound a non-loopback interface; the current server is loopback-only.
- T3 Connect relay: the stale backend held a second relay tunnel under the same environment ID, so relay traffic could also land on it.
The phone speaks the v2 protocol (its connections to the other machines succeeded against current servers), so every path to this machine failed at the handshake against the v1 backend. Server traces confirmed zero non-loopback clients ever reached the current server.
What made this hard to diagnose and hard to fix:
- Nothing warned that two servers shared one environment ID.
t3 doctor --dry-rundetected none of it (only DB maintenance).- The serve config outlived its owner: after the desktop app was closed and uninstalled, nothing re-pointed the config, so the tailnet HTTPS endpoint stayed dead until
tailscale servewas fixed by hand. - The durable fix (
T3CODE_TAILSCALE_SERVE/T3CODE_TAILSCALE_SERVE_PORT) exists in the binary but is undocumented int3 serve --help, so discovering it took binary string inspection.
Steps to reproduce
- Install the desktop app from the system package channel (e.g. AUR
t3code-nightly-bin) while a pre-0.1.0 release is current. On Arch this package is only updated by an explicit package-manager upgrade, so it can stay behind indefinitely. - In its Settings → Connections, enable Tailscale HTTPS. This pins
tailscale serveto the desktop backend's own port, with no re-reconciliation afterwards. - Separately install the CLI via npm/npx and run
t3 service install. The service self-updates across protocol boundaries; the desktop app does not. - Add the machine to T3 Connect on a mobile client.
- Observe: the machine is listed, but the mobile client cannot connect. The tailnet HTTPS endpoint serves the stale backend (protocol v1). Removing the desktop app does not recover the endpoint.
Version
0.1.1-nightly.20261020.3386 (coexisting with 0.0.43-nightly.20260923.2135 desktop backend)
Environment
Arch Linux on WSL2 (kernel 6.18.54.1-microsoft-standard-WSL2), Node v26.8.2, boot service via systemd user unit, Tailscale running inside the VM, android T3 Connect client
Evidence
# before fix: tailnet HTTPS endpoint serves the stale backend
$ tailscale serve status
https://<machine>.<tailnet>.ts.net:20443 (tailnet only)
|-- / proxy http://127.0.0.1:3774
$ curl -s https://<machine>.<tailnet>.ts.net:20443/.well-known/t3/environment
{"serverVersion":"0.0.43-nightly.20260923.2135","orchestrationProtocolVersion":1,...}
# current server: loopback only, protocol v2
$ ss -tlnp | grep -E '3773|3774'
LISTEN 127.0.0.1:3773 users:(("node",pid=2309786)) # 0.1.1-nightly boot service
LISTEN 0.0.0.0:3774 users:(("t3code",pid=7795)) # 0.0.43-nightly desktop backend
$ curl -s http://127.0.0.1:3773/.well-known/t3/environment
{"serverVersion":"0.1.1-nightly.20261020.3386","orchestrationProtocolVersion":2,...}
# phone online and pingable (8ms), yet the current server's trace
# contains only loopback clients — no attempt ever arrived
$ grep -ac '"client.address":"<phone-tailnet-ip>"' server.trace.ndjson
0
# stale backend also ran its own relay tunnel (child process):
$ ps -p 7795 -o pid,args= ; ps --ppid 7795 -o pid,args=
7795 ... /opt/t3code-nightly-bin/t3code .../bin.mjs --bootstrap-fd 3
895889 ... cloudflared tunnel run
# after fix
$ tailscale serve status
https://<machine>.<tailnet>.ts.net:20443 (tailnet only)
|-- / proxy http://127.0.0.1:3773
Related issues
#4028 — shares the WSL + Tailscale Serve area but is different: there, serve is never configured for a WSL-only backend; here, serve is silently owned by a stale package-installed backend that also duplicates the relay tunnel under one environment ID.
Fix applied or workaround
tailscale serve --bg --https=20443 http://127.0.0.1:3773(immediate, no restart)- Added
T3CODE_TAILSCALE_SERVE=1/T3CODE_TAILSCALE_SERVE_PORT=20443to the systemd user unit so the boot service re-asserts the config at future restarts - Quit and uninstalled the stale desktop app (
pacman -Rns t3code-nightly-bin), removing its backend and duplicate relay tunnel
The android phone then connected to the current server successfully.
Suggestions: (a) the two install channels should at least notice each other — a newer server finding a stale desktop backend in the same data dir (or vice versa) should warn instead of silently coexisting; (b) t3 doctor or server startup should flag a serve config pointing at a port no live T3 process owns, and re-point or offer to; (c) document T3CODE_TAILSCALE_SERVE*.
Filed by
MiMo (opencode/mimo-v2.6-flash-free) via t3 triage
Activity
juliusmarminge commentedon Oct 23, 2026
Checked current main 66ee628e08 on Linux with isolated T3 homes. Started the exact
0.0.43-nightly.20260923.2135backend in desktop mode on port 24564; its environment endpoint reported protocol v1. Starting current main on 24565 with that same home exited 78 withServerAlreadyRunningError, left the old runtime record intact, and never opened the second port. A current dev server plus a second current desktop-mode server was also refused. All 10serverRuntimeState.test.tstests passed, including legacy-server and older npm boot-service detection.The shared-home coordination is addressed by 570542662a and 353d8c55d8. This prevents the reported simultaneous backends when starting the current server beside the old desktop backend; it does not repair an existing stale Serve mapping. That remaining mapping ownership/reconciliation problem is tracked in #10363. Current
t3 serve --helpalso exposes--tailscale-serveand--tailscale-serve-port. Closing this report as addressed by the existing coordination fixes, with Serve reconciliation retained in the existing issue.