Repository navigation
Remote server self-update leaves the installed t3 CLI pointing at the previous runtime #14609
Description
Activity
Note
Grok responding on behalf of Julius.
Triage
Thanks for the detailed write-up, @rohitgirdhar. The before and after
readlinkoutput and the service log lines made this easy to trace. I confirmed it onmainat5cc99e1c23980d7995a13c47f969b47cb68ed1be. 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 underT3CODE_INSTALL_BIN_DIR) as a symlink into this home's runtime tree:Lines 219 to 221 in 5cc99e1
step "Setting up the t3 command..." mkdir -p "$bin_dir" ln -sfn "${target_dir}/t3" "${bin_dir}/t3" Only
t3 updaterepoints that symlink, and it does so after the new runtime is installed:t3code/apps/server/src/cli/update.ts
Lines 539 to 544 in 5cc99e1
const launchedAs = (yield* HostProcessIsExecutable) ? yield* resolveLauncherPath : undefined; const repointed = yield* repointLauncher({ launchedAs, versionsDir: path.dirname(runtime.versionDir), targetEntryPath: runtime.entryPath, }); 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:
t3code/apps/server/src/cloud/selfUpdate.ts
Lines 313 to 332 in 5cc99e1
yield* reportProgress("installing"); const updateId = yield* Effect.uninterruptible( launcher.requestUpdate({ targetVersion, dbPath: serverConfig.dbPath }).pipe( Effect.mapError((error) => failWith( error._tag === "ServiceLauncherRejectedError" ? error.reason : "Could not ask the service launcher to activate the prepared update.", error, ), ), Effect.tap(() => onHandoffAccepted()), ), ); yield* Effect.logInfo("Server update prepared; handing off to the service launcher.", { updateId, targetVersion, runtimePath: paths.entryPath, }); The launcher commits
activeVersioninservice-state.jsonand starts that runtime. It never touches~/.local/bin/t3:t3code/apps/server/src/serviceLauncher.ts
Lines 553 to 563 in 5cc99e1
const committed = terminalUpdate({ pending, status: "committed" }); const next: ServiceState = { ...this.#state, activeVersion: pending.targetVersion, update: committed, }; await writeServiceState(this.#statePath, next); this.#state = next; child.role = "active"; await discardDatabaseBackup(this.#baseDir, committed.id).catch(() => undefined); await sendMessage(child.process, { type: "committed", updateId: committed.id }); 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/environmentreports the new server. That's the mismatch you saw.Repointing from the handoff in
selfUpdate.tswouldn'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 movet3onto a version that later rolls back. The new runtime only knows the update stuck onceprepareTrialreturnscommitted:t3code/apps/server/src/serverRuntimeStartup.ts
Lines 1046 to 1048 in 5cc99e1
// This is the prepared boundary. Every dependency has been acquired and // every runtime root has confirmed that it is parked before this request. const updateOutcome = yield* launcher.prepareTrial; Related, but not duplicates
- Server-update preflight blocks recovery, but
t3 service updatereports success from a stale CLI that shadows the updated one #13239 (open issue): there, an oldert3earlier onPATHshadowed the intended install. Here the installer's own symlink is the one onPATH, and it gets left behind. fix(server): launcher preflight and service install explain which t3 ran #13804 (open PR) improves the recovery message for Server-update preflight blocks recovery, butt3 service updatereports success from a stale CLI that shadows the updated one #13239 but doesn't move this symlink. - fix(install): launch t3 by absolute path #13794 (open PR) proposes replacing the installer symlink with an absolute-path wrapper. The installer still uses
ln -sfntoday. - fix(server): t3 run from a T3 terminal no longer trips on the service's launcher context #14263 (open PR) is about a service environment variable leaking into terminals. It's unrelated.
Likely fix
Once the new runtime's
prepareTrialreturnscommitted, it should repoint any user launcher this home owns. That means checking~/.local/bin(orT3CODE_INSTALL_BIN_DIR) andPATH, using the existing ownership rule: only symlinks, or a Windowst3.cmdshim, that point into this home'sruntime/versions. A missing launcher is normal on desktop-managed andnpxhosts, 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 localt3 updateandt3 service installpath.Until that lands, your workaround is right: point the symlink at
~/.t3/runtime/versions/<activeVersion>/t3. Runningt3 update <activeVersion>on the host also works, but it rewrites the service unit and restarts the service when you confirm.Reacted by Rohit Girdhar- Server-update preflight blocks recovery, but
- addedbugSomething is broken or behaving incorrectly.Something is broken or behaving incorrectly.via-triageFiled through npx t3 triageFiled through npx t3 triage
on Oct 1, 2026 Another consequence beyond the version mismatch: pairing links created by the stale CLI are rejected. After updating from the desktop app to
0.0.46-nightly.20261009.2873,~/.local/bin/t3was still 0.0.44, andt3 auth pairing createfrom it produced links the server refused. The client only showed "The environment credential is invalid." Running~/.t3/runtime/versions/<active>/t3 auth pairing createworked.
What happened
Updating the remote T3 Code background server from the desktop app succeeds, but the installed
t3command remains on an older release. This makest3 --versiondisagree with the running server and makes it unclear whether the update worked.Observed on October 1, 2026: the server reported
0.0.45-nightly.20261001.2525, whilet3 --versionreported0.0.44-nightly.20260929.2456. After manually repointing the CLI to .2525, a subsequent server update selected .2539 and left the CLI on .2525 again.Diagnosis
The CLI shortcut is a symlink into
~/.t3/runtime/versions/<version>/t3. The background service uses a stable launcher and selects its active runtime separately.In release commit
148e6deea046658639aae9fef5b349781cec39d1:t3 update.The old service-launcher executable is intentionally separate from the active server runtime. This report concerns the user-facing CLI shortcut, not the service's stable-launcher architecture.
Steps to reproduce
~/.local/bin/t3pointing into the runtime versions directory, and install the background service./.well-known/t3/environment.t3 --versionand inspectreadlink ~/.local/bin/t3.Expected: the installed CLI follows the successfully committed update, or the app clearly explains and offers a way to reconcile the versions.
Actual: the server updates, but the CLI symlink and CLI version remain on the previous runtime.
Version
CLI initially
0.0.44-nightly.20260929.2456; server0.0.45-nightly.20261001.2525. Recurrence after the CLI was aligned: server/service active runtime .2539, CLI .2525.Environment
Linux x64 VM, kernel
6.11.0-1016-nvidia, systemd user service, self-contained T3 installation. Connected from the macOS Nightly desktop app. Triage context reports embedded Nodev26.8.2.Evidence
Home paths are redacted:
Related issues
#13239 also involves a stale CLI, but there a separate older installation shadows the intended CLI on PATH. Here the documented installer's symlink itself stays on the older runtime after an app-driven update. No exact duplicate was found.
Fix applied or workaround
Manually repointed
~/.local/bin/t3to the .2525 runtime and verified that CLI and server matched. No server restart, source patch, or database changes were needed. The next server update reproduced the mismatch.Filed by
GPT-6.1-Sol through the Codex harness in T3 Code, following
t3 triage's generated context and current upstream playbook. Used the command's prompt-file fallback in this existing agent session.