Skip to content

Remote server self-update leaves the installed t3 CLI pointing at the previous runtime #14609

Description

@rohitgirdhar

What happened

Updating the remote T3 Code background server from the desktop app succeeds, but the installed t3 command remains on an older release. This makes t3 --version disagree 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, while t3 --version reported 0.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:

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

  1. Install T3 using the standalone installer, with ~/.local/bin/t3 pointing into the runtime versions directory, and install the background service.
  2. Connect from the desktop app and update that server to a newer nightly using Update server.
  3. Wait for reconnection and verify the new version through /.well-known/t3/environment.
  4. Run t3 --version and inspect readlink ~/.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; server 0.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 Node v26.8.2.

Evidence

Home paths are redacted:

# Before the manual workaround
$ t3 --version
t3 v0.0.44-nightly.20260929.2456
$ readlink ~/.local/bin/t3
~/.t3/runtime/versions/0.0.44-nightly.20260929.2456/t3
Running server: 0.0.45-nightly.20261001.2525

# Service log, October 1, 2026, UTC
13:47:07.301 Server update prepared; handing off to the service launcher.
targetVersion: 0.0.45-nightly.20261001.2525
14:23:37.425 Server update prepared; handing off to the service launcher.
targetVersion: 0.0.45-nightly.20261001.2539

# After manually aligning the CLI to .2525, then another server update
CLI: 0.0.45-nightly.20261001.2525
service-state.json: protocol 3, activeVersion 0.0.45-nightly.20261001.2539

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/t3 to 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.

Activity

  1. juliusmarminge commented on Oct 1, 2026

    @juliusmarminge
    Member

    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:

    t3code/scripts/install.sh

    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 update repoints that symlink, and it does so after the new runtime is installed:

    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:

    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 activeVersion in service-state.json and starts that runtime. It never touches ~/.local/bin/t3:

    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/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:

    // 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

    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.

  2. added
    bugSomething is broken or behaving incorrectly.
    via-triageFiled through npx t3 triage
    on Oct 1, 2026
  3. alexisbeaulieu97 commented on Oct 9, 2026

    @alexisbeaulieu97

    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/t3 was still 0.0.44, and t3 auth pairing create from it produced links the server refused. The client only showed "The environment credential is invalid." Running ~/.t3/runtime/versions/<active>/t3 auth pairing create worked.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions