From human perspective, I would generally expect a yay -S to update everything. The separation between 't3' and 't3code' is difficult to reason about. I don't understand if I'm using t3code-nightly, do I also need to be on a 'nightly' of t3 and if so, how do I do that? Would using npx updates and yay updates step on each other? It seems consistent with the design philosophy so far to make the whole thing more clear, self-diagnosing and self-healing. Thank you~
Server-update preflight blocks recovery, but `t3 service update` reports success from a stale CLI that shadows the updated one#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'slauncherProtocolagainst the target release'sSERVICE_LAUNCHER_PROTOCOL(3) and blocks with the "requires a newer T3 Code service launcher" message shown in the client, naming nothing about whicht3it found.apps/server/src/cli/service.ts:156(also ~L165 for thet3 service updatedeprecated-alias handler) —t3 service updateandt3 service installreconcile againstpackageJson.versionof the CLI running the command. On a machine whose intended CLI (~/.local/bin/t3from the documentedinstall.sh) never got installed, a month-old globally-npm-installedt3@0.0.40under nvm's bin dir (~/.nvm/versions/node/v26.8.2/bin/t3) was earlier onPATHthan 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 withpacman -Ql) ships only the desktop app (/usr/bin/t3code-nightly, electron files) — not3CLI binary. Docs tell users to install the CLI viacurl | shwhich putst3in~/.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 updatewith no--channelflag defaulted to stable 0.0.42 even though the machine tracks the nightly train (client and AUR package are on0.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
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
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."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."
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
| 1 | $ which -a t3 |
| 2 | ~/.nvm/versions/node/v26.8.2/bin/t3 # → .../lib/node_modules/t3/dist/bin.mjs, t3 v0.0.40 |
| 3 | |
| 4 | $ cat ~/.t3/runtime/service-state.json # before |
| 5 | {"protocol": 2, "activeVersion": "0.0.40"} |
| 6 | |
| 7 | $ t3 service update |
| 8 | T3 Code service is already using t3@0.0.40. # app/cli/service.ts — matches *its own* packageJson.version |
| 9 | |
| 10 | # same cycle repeats every time the client asks; ~/.t3/runtime/versions already |
| 11 | # 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)
Triage
Confirmed on main at f5ef0ddb9. The nightly in this report (0.0.43-nightly.20260923.2135) already contains it. Not already fixed.
What you reported. The desktop client blocked a server update with "This release requires a newer T3 Code service launcher. Update it on the server machine." Updating the Arch desktop package and running t3 service update printed T3 Code service is already using t3@0.0.40. and the notice came back. which -a t3 showed an nvm global t3@0.0.40 ahead of any ~/.local/bin/t3. The service state stayed {"protocol": 2, "activeVersion": "0.0.40"}, with a staged 0.0.43-nightly.20260921.2044 runtime left unused. Installing a nightly CLI and running t3 service install moved the service to protocol 3 and cleared the notice.
The block itself is the protocol gate from #11940. The bug is that the recovery path reports success without saying which t3 ran, and the only release that satisfies this client is a nightly CLI. Stable latest is still protocol 2.
What the code does
1. The in-app update is supposed to stop here.
The running 0.0.40 server calls the staged binary's __service-preflight with its own launcher protocol, which is 2:
https://github.com/pingdotgg/t3code/blob/v0.0.40/apps/server/src/cloud/selfUpdate.ts#L225-L229
https://github.com/pingdotgg/t3code/blob/v0.0.40/apps/server/src/cloud/serviceProtocol.ts#L4
The staged nightly requires protocol 3 and returns that exact sentence. Nothing in the sentence names a command, a protocol number, or a path:
#11940 added this on 2026-09-15 so an old launcher would not activate a standalone runtime. It is not in stable v0.0.42 (still protocol 2). It is in nightlies from v0.0.41-nightly.20260916.1795 onward. The staged 0.0.43-nightly.20260921.2044 tree is the preflight failing before the launcher is asked to switch. That part of the report matches the code.
2. On 0.0.40, t3 service update only repairs the CLI that ran it.
v0.0.40 has no t3 update command and no deprecation warning. t3 service update is the real command, and its description says it uses this CLI version (npx t3@latest service update for a newer one). When the unit, sentinel, and service-state.json already match that CLI, it prints the line you saw and exits 0:
https://github.com/pingdotgg/t3code/blob/v0.0.40/apps/server/src/cli/service.ts#L136-L146
"Already using t3@0.0.40" was true for that process. It does not print execPath, so an nvm shim earlier on PATH is invisible. The deprecated-alias text on current main was not what ran: that copy landed in #11702 and is in v0.0.42+, not in 0.0.40.
Current main prints the deprecation, then the same version-only success line, still with no path. status.current is "this process's version already matches the unit and state," not "this machine has the release the client asked for":
3. Following the old help text still cannot clear a nightly client.
npx t3@latest and t3 update with no --channel both follow stable. Latest stable is v0.0.42, which is still protocol 2, so the preflight blocks again. The channel flag defaults to the channel of the CLI that ran, not the desktop app:
install.sh does the same (T3CODE_CHANNEL defaults to stable). A nightly desktop package does not change that. t3code-nightly-bin is the AppImage only; it does not install a t3 binary or touch the systemd user service:
The host command in the user docs is t3 update <client-version>. The preflight string never says that, and v0.0.40 cannot run it:
t3 update <nightly> from a protocol-2 CLI that does have the command (v0.0.42) is the crash-loop in #12627, still open. #12629 is not merged, and main has no older-state adoption. The workaround that worked here avoids that: put a protocol-3 CLI first on PATH, then t3 service install.
Not a duplicate
- #11934, closed by #11940 — the preflight did not fire, so the update failed with a misleading launcher error. Here the preflight fired, as designed.
- #12020, closed duplicate — a stale
service-launcher.mjscrash-looped on a 0.0.42 runtime. This service stayed up on 0.0.40. - #12627, open, and #12629 —
t3 updateacross the protocol bump writes protocol 2 next to the new version and crash-loops while reporting success. This host never left 0.0.40 and did not crash-loop.
No open issue covers the preflight telling you only to "update on the server machine," or t3 service update reporting success from whichever t3 is first on PATH.
Fix direction
The string users see is produced by the staged nightly, so a change there reaches hosts still on protocol 2.
Say which protocol was offered and which this release requires. Name the host command that actually moves the launcher: install this exact version (T3CODE_VERSION=<client-version> on https://t3.codes/install.sh, or t3 update <client-version> once that CLI is what t3 is), then t3 service install. Say that t3 service update, npx t3@latest, and t3 update with no version stay on the CLI that is first on PATH, and that a desktop or AUR upgrade does not replace it. Point at t3 --version and which -a t3 when that version is not the one in the notice.
On current CLIs, include the executable path in the "already using" / "already installed" lines. That does not rewrite 0.0.40; the preflight string is what those hosts see.
Leave the channel default as it is. A stable CLI updating to stable is correct. Do not treat "AUR has no t3 binary" as a defect.
Accepting as a recovery-message bug. The protocol block stays.