Skip to content

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

Closed

Description

@mdmdj

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:

  1. 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.
  2. Direct tailnet/LAN IP: only the stale backend bound a non-loopback interface; the current server is loopback-only.
  3. 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-run detected 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 serve was fixed by hand.
  • The durable fix (T3CODE_TAILSCALE_SERVE / T3CODE_TAILSCALE_SERVE_PORT) exists in the binary but is undocumented in t3 serve --help, so discovering it took binary string inspection.

Steps to reproduce

  1. 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.
  2. In its Settings → Connections, enable Tailscale HTTPS. This pins tailscale serve to the desktop backend's own port, with no re-reconciliation afterwards.
  3. Separately install the CLI via npm/npx and run t3 service install. The service self-updates across protocol boundaries; the desktop app does not.
  4. Add the machine to T3 Connect on a mobile client.
  5. 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

  1. tailscale serve --bg --https=20443 http://127.0.0.1:3773 (immediate, no restart)
  2. Added T3CODE_TAILSCALE_SERVE=1 / T3CODE_TAILSCALE_SERVE_PORT=20443 to the systemd user unit so the boot service re-asserts the config at future restarts
  3. 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

  1. added
    needs more infoInitial triage showed no bug. Awaiting more info
    and removed
    needs more infoInitial triage showed no bug. Awaiting more info
    on Oct 22, 2026
  2. juliusmarminge commented on Oct 23, 2026

    @juliusmarminge
    Member

    Checked current main 66ee628e08 on Linux with isolated T3 homes. Started the exact 0.0.43-nightly.20260923.2135 backend in desktop mode on port 24564; its environment endpoint reported protocol v1. Starting current main on 24565 with that same home exited 78 with ServerAlreadyRunningError, 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 10 serverRuntimeState.test.ts tests 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 --help also exposes --tailscale-serve and --tailscale-serve-port. Closing this report as addressed by the existing coordination fixes, with Serve reconciliation retained in the existing issue.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions