# Server-update preflight blocks recovery, but \`t3 service update\` reports success from a stale CLI that shadows the updated one · Issue \#13239 · pingdotgg/t3code

[View on GitCafe](https://git.cafe/pingdotgg/t3code/issues/13239)

Repository: [pingdotgg/t3code](https://git.cafe/pingdotgg/t3code)

Visibility: public

State: completed

Priority: 0

## Description

[Discussion and activity](https://git.cafe/pingdotgg/t3code/issues/13239)

---

### What happened

The desktop client told me the server release required a newer service launcher ("This release requires a newer T3 Code service launcher. Update it on the server machine."). On the server machine I updated the `t3code-nightly-bin` AUR package via `yay -S` and ran `t3 service update`, which replied `T3 Code service is already using t3@0.0.40.` — every command reported success, yet the update notice kept coming back indefinitely.

Full user error on my part for having a second, stale `t3` CLI earlier on `PATH`, but I'd like to report it because the tooling kept telling me I *had* updated, with no way to spot that the command was operating on the wrong CLI.

### Diagnosis

Grounded in the installed source for 0.0.40-nightly:

- `apps/server/src/cloud/servicePreflight.ts:28` — the preflight compares the running (old, protocol-2) CLI's `launcherProtocol` against the target release's `SERVICE_LAUNCHER_PROTOCOL` (3) and blocks with the "requires a newer T3 Code service launcher" message shown in the client, naming nothing about *which* `t3` it found.
- `apps/server/src/cli/service.ts:156` (also ~L165 for the `t3 service update` deprecated-alias handler) — `t3 service update` and `t3 service install` reconcile against `packageJson.version` of the CLI running the command. On a machine whose intended CLI (`~/.local/bin/t3` from the documented `install.sh`) never got installed, a month-old globally-npm-installed `t3@0.0.40` under nvm's bin dir (`~/.nvm/versions/node/v26.8.2/bin/t3`) was earlier on `PATH` than both, so the command ran against 0.0.40, found state already matching 0.0.40, and reported success. This explains how a user can keep "updating" and still be blocked.
- The Arch AUR package `[t3code-nightly](https://github.com/usr/bin/t3code-nightly)-bin` (verified with `pacman -Ql`) ships only the desktop app (`/usr/bin/t3code-nightly`, electron files) — no `t3` CLI [bin](https://github.com/home/mdm/.local/bin)ary. Docs tell users to install the CLI via `curl | sh` which puts `t3` in `~/.local/bin`; nothing warns that a different CLI install method (global npm from an earlier attempt, e.g. under a version manager) shadows it.
- Independently, `t3 update` with no `--channel` flag defaulted to **stable** 0.0.42 even though the machine tracks the nightly train (client and AUR package are on `0.0.43_nightly`); the channel only followed the CLI's own channel after an explicit `--channel nightly`. Easy other-version confusion this way too.

### Steps to reproduce

1. Install t3 into a node-version-manager global dir and leave it first on PATH
nvm use 26 && npm i -g t3@0.0.40
never run the official installer (curl -fsSL https://t3.codes/install.sh | sh),
so ~/.local/bin/t3 does not exist

2. Update the desktop app (e.g. `yay -S t3code-nightly-bin`) to a release whose
launcher protocol > 2, pair, and let the client ask for a server update.
Client shows: "This release requires a newer T3 Code service launcher.
Update it on the server machine."

3. On the server machine, follow what looks like the update procedure:
pacman -S t3code-nightly-bin    # or yay/pacman equivalent
t3 service update               # → "T3 Code service is already using t3@0.0.40."

4. Reconnect — the same launcher-protocol error appears again.
There is no error from step 3: it succeeded against the shadowed CLI.


### Version

0.0.40 (server, pre-fix) → 0.0.43-nightly.20260923.2135 (client and server after fix)

### Environment

Linux x64 WSL2 (Arch, kernel 6.18.33.2-microsoft-standard-WSL2), Node v26.8.2 via nvm, systemd user service, desktop client `t3code-nightly-bin 0.0.43_nightly.20260923.2135-1

### Evidence

```shell
$ which -a t3
~/.nvm/versions/node/v26.8.2/bin/t3   # → .../lib/node_modules/t3/dist/bin.mjs, t3 v0.0.40

$ cat ~/.t3/runtime/service-state.json    # before
{"protocol": 2, "activeVersion": "0.0.40"}

$ t3 service update
T3 Code service is already using t3@0.0.40.   # app/cli/service.ts — matches *its own* packageJson.version

# same cycle repeats every time the client asks; ~/.t3/runtime/versions already
# contains the unused staged runtime 0.0.43-nightly.20260921.2044 before the fix
```

### Related issues

#12020 ( CLOSED, duplicate) — stale `service-launcher.mjs` on disk with a new runtime layout; here the launcher file was fine, the *CLI* was the thing that never updated. #12627 — `t3 update` across a protocol bump crash-loops the service while claiming success; different failure mode (no crash loop/no state corruption here, just a silent no-op loop). #11934 — self-update against a pre-#11510 launcher; again a launcher-file problem, not a stale/CLI-shadowed-CLI problem.

### Fix applied or workaround

npm uninstall -g t3                     # remove the shim shadowing the newer CLI
T3CODE_CHANNEL=nightly curl -fsSL https://t3.codes/install.sh | sh
t3 update --channel nightly --yes       # explicit channel needed, defaults to the running CLI's channel otherwise
t3 service install                      # rewrote service-state.json to protocol 3 + active nightly

$ cat ~/.t3/runtime/service-state.json  # after
{"protocol": 3, "activeVersion": "0.0.43-nightly.20260923.2135"}

Service came up healthy on the new version; the update notice cleared.

### Filed by

t3 triage (agent: opencode, model: glm-5.3-flash)
