Server update to 0.1.3-nightly.20261025.3692 rolls back with EADDRINUSE when Tailscale Serve shares the server's port#25161

opened4d agobyKkcazin

What happened

The user ran a server update from the T3 Code desktop app. It should have upgraded the background service on their Linux machine from 0.1.3-nightly.20261023.3566 to 0.1.3-nightly.20261025.3692, "but apparently it failed". The server stayed on the old version. Retrying gave the same result: three attempts at 09:01, 09:03 and 09:05 local time all rolled back.

Diagnosis

  • #24588 changed the default listen address. A server started by the service launcher, with no host set by flag, T3CODE_HOST or bootstrap, now binds 0.0.0.0. Before, it fell through to 127.0.0.1. See apps/server/src/cli/config.ts#L412-L424 and the config.host ?? "127.0.0.1" fallback in apps/server/src/server.ts#L313.
  • This machine exposes the server on its tailnet with Tailscale Serve, using the same port number: https://<host>.<tailnet>.ts.net:3773 proxies to http://127.0.0.1:3773. To do that, tailscaled listens on the tailnet addresses at port 3773.
  • Linux rejects a wildcard bind on a port that a specific local address already holds, even with SO_REUSEADDR. The upgrade candidate therefore exits 1 at listen() with EADDRINUSE 0.0.0.0:3773. The launcher correctly rolls back (candidate-exited:1), and every retry fails the same way. The old build never conflicted because it bound only 127.0.0.1.
  • The documented opt-out, T3CODE_HOST=127.0.0.1 in service.env, does not help this install. The systemd unit's ExecStart still runs the launcher from 0.0.46-nightly.20261014.3093, the version pinned when t3 service install ran. That launcher does not read service.env (there is no readServiceEnvironment at that tag), and the unit is only rewritten when t3 service install runs again. Any install whose pinned launcher predates service.env support silently ignores the opt-out.
  • Possible directions: when the implicit wildcard bind fails with EADDRINUSE, fall back to 127.0.0.1 with a warning, so an update cannot break a working loopback setup. An explicit host should still fail hard. Separately, the desktop client could show the rollback reason.

Steps to reproduce

  1. On Linux, install the background service with t3 service install on a build before #24588, such as 0.1.3-nightly.20261023.3566. The server listens on 127.0.0.1:3773.
  2. Expose it on the tailnet at the same port: tailscale serve --bg --https=3773 http://127.0.0.1:3773. Any process listening on a specific non-loopback local address at port 3773 has the same effect.
  3. From the desktop app, update the server to 0.1.3-nightly.20261025.3692.
  4. The candidate exits with listen EADDRINUSE: address already in use 0.0.0.0:3773, the launcher rolls back, and the server stays on the old version.

The kernel behavior reproduces on its own, with a spare port standing in for 3773:

js
1// node probe.mjs
2import net from "node:net";
3const a = net.createServer().listen(39773, "100.x.y.z" /* this machine's tailnet IP */, () => {
4 net.createServer().once("error", (e) => console.log("0.0.0.0 ->", e.code)).listen(39773, "0.0.0.0"); // EADDRINUSE
5 net.createServer().once("error", (e) => console.log("127.0.0.1 ->", e.code)).listen(39773, "127.0.0.1", () => console.log("127.0.0.1 -> OK"));
6});

Version

Server 0.1.3-nightly.20261023.3566, failing to update to 0.1.3-nightly.20261025.3692. Service launcher 0.0.46-nightly.20261014.3093. Desktop app version unknown. The triage CLI is 0.1.2.

Environment

Linux x64 (Omarchy/Arch, kernel 7.2.5), systemd user service t3code.service, Tailscale 1.102.3 (tailscale serve on :3773), Node v26.12.1 on the host.

Evidence

shell
1# ~/.t3/userdata/logs/boot-service.log (tunnel/relay noise removed)
2[09:01:41.617] INFO: Server update prepared; handing off to the service launcher.
3 targetVersion: '0.1.3-nightly.20261025.3692'
4[09:02:01.848] ERROR (#2): ServeError:
5 at Server.onError (file://~/.t3/runtime/versions/0.1.3-nightly.20261025.3692/t3:149952:19)
6 at server.runServerCommand (file://~/.t3/runtime/versions/0.1.3-nightly.20261025.3692/t3:416884:203)
7 [cause]: Error: listen EADDRINUSE: address already in use 0.0.0.0:3773
8[09:02:23.393] INFO (#6513): Listening on http://127.0.0.1:3773 # rolled back to 0.1.3-nightly.20261023.3566
9# identical sequence at 09:03:46 -> 09:04:04 and 09:05:43 -> 09:05:59
10
11# ~/.t3/runtime/service-state.json after the third attempt
12{ "protocol": 3, "activeVersion": "0.1.3-nightly.20261023.3566",
13 "update": { "fromVersion": "0.1.3-nightly.20261023.3566", "targetVersion": "0.1.3-nightly.20261025.3692",
14 "status": "rolled-back", "reason": "candidate-exited:1" } }
15
16# ss -ltn 'sport = :3773'
17LISTEN 100.x.y.z:3773 # tailscaled (tailscale serve --https=3773 -> http://127.0.0.1:3773)
18LISTEN 127.0.0.1:3773 # t3 serve 0.1.3-nightly.20261023.3566
19LISTEN [fd7a:115c:a1e0::…]:3773 # tailscaled
20
21# systemctl --user cat t3code.service
22ExecStart=~/.t3/runtime/versions/0.0.46-nightly.20261014.3093/t3 __service-launcher

Related issues

None found for EADDRINUSE, candidate-exited, rolled-back updates or Tailscale Serve port conflicts. #24225 (launcher idles after an SSH-launched server takes the owner lock) is a different failure in the same launcher. #9056 (WSL backend binds 0.0.0.0) is a different surface and cause.

Fix applied or workaround

Added a systemd drop-in, which reaches the old launcher because it passes its environment to every child it starts:

1# ~/.config/systemd/user/t3code.service.d/60-host.conf
2[Service]
3Environment=T3CODE_HOST=127.0.0.1

followed by systemctl --user daemon-reload && systemctl --user restart t3code.service. A retry from the desktop app then committed: the server runs 0.1.3-nightly.20261025.3692 on 127.0.0.1:3773, and Tailscale Serve still reaches it. The trade-off is that this install doesn't get #24588's direct LAN connections.

Filed by

Claude Code (Claude Opus 5.5, claude-opus-5-5) via npx t3 triage

Synced from GitHub

Note

🤖 Claude Opus 5.5 responding on behalf of Nick

Same failure on macOS. It isn't specific to Linux or systemd.

The server runs as a launchd agent (com.t3tools.t3code.service.plist, launcher 0.0.43-nightly.20260925.2251) behind Mac App Store Tailscale, with tailscale serve --https=3773 http://127.0.0.1:3773. Updating from 0.1.3-nightly.20261024.3630 to 0.1.3-nightly.20261025.3692 failed the same way:

1[20:31:29.336] INFO: Server update prepared; handing off to the service launcher.
2 [cause]: Error: listen EADDRINUSE: address already in use 0.0.0.0:3773
3[20:31:33.492] INFO: Listening on http://127.0.0.1:3773 # rolled back, candidate-exited:1

netstat shows io.tailscale.ipn listening on 100.x.y.z.3773 and on the tailnet IPv6 address at port 3773, next to t3 on 127.0.0.1.3773.

The T3CODE_HOST workaround also works on macOS. Add it to the plist's EnvironmentVariables. launchd only re-reads the plist on bootout/bootstrap; kickstart doesn't pick up the change:

sh
1P=~/Library/LaunchAgents/com.t3tools.t3code.service.plist
2plutil -replace EnvironmentVariables.T3CODE_HOST -string 127.0.0.1 "$P"
3launchctl bootout gui/$(id -u)/com.t3tools.t3code.service
4launchctl bootstrap gui/$(id -u) "$P"

After a retry from the desktop app, the update committed on 3692.

The same thing happened on two Linux boxes with systemd user services. One had launcher 0.0.43-nightly.20260918.1895, the other 0.1.3-nightly.20261024.3643. Both were fixed by the drop-in described in the issue. A machine whose service already had T3CODE_HOST=127.0.0.1 set updated without problems. In this fleet, every service-managed server that shares its port with Tailscale Serve failed this update.