Note
Grok responding on behalf of Julius.
Triage
Thanks for the detailed write-up, @rohitgirdhar. The before and after readlink output and the service log lines made this easy to trace. I confirmed it on main at 5cc99e1c23980d7995a13c47f969b47cb68ed1be. The release commit you cited (148e6deea046658639aae9fef5b349781cec39d1) is an ancestor of that commit, and this code path hasn't changed since, so it isn't already fixed.
What's happening
The standalone installer creates ~/.local/bin/t3 (or the same name under T3CODE_INSTALL_BIN_DIR) as a symlink into this home's runtime tree:
Only t3 update repoints that symlink, and it does so after the new runtime is installed:
The desktop app's Update server action never calls that step. The running server downloads and preflights the target, then hands it to the service launcher:
The launcher commits activeVersion in service-state.json and starts that runtime. It never touches ~/.local/bin/t3:
Keeping the stable launcher separate is intentional, because it lets a failed trial roll back (server-updates.md). The side effect is that after a remote update the CLI symlink stays on the old runtime while /.well-known/t3/environment reports the new server. That's the mismatch you saw.
Repointing from the handoff in selfUpdate.ts wouldn't work. That code runs in the outgoing server, which wasn't started through ~/.local/bin/t3, and it runs before the trial commits, so it could move t3 onto a version that later rolls back. The new runtime only knows the update stuck once prepareTrial returns committed:
Related, but not duplicates
- #13239 (open issue): there, an older
t3earlier onPATHshadowed the intended install. Here the installer's own symlink is the one onPATH, and it gets left behind. #13804 (open PR) improves the recovery message for #13239 but doesn't move this symlink. - #13794 (open PR) proposes replacing the installer symlink with an absolute-path wrapper. The installer still uses
ln -sfntoday. - #14263 (open PR) is about a service environment variable leaking into terminals. It's unrelated.
Likely fix
Once the new runtime's prepareTrial returns committed, it should repoint any user launcher this home owns. That means checking ~/.local/bin (or T3CODE_INSTALL_BIN_DIR) and PATH, using the existing ownership rule: only symlinks, or a Windows t3.cmd shim, that point into this home's runtime/versions. A missing launcher is normal on desktop-managed and npx hosts, and a failed rewrite should only be logged, never roll back an update that has already committed. The systemd or launchd unit and the stable launcher can stay on the local t3 update and t3 service install path.
Until that lands, your workaround is right: point the symlink at ~/.t3/runtime/versions/<activeVersion>/t3. Running t3 update <activeVersion> on the host also works, but it rewrites the service unit and restarts the service when you confirm.