Skip to content

[Bug]: t3 update writes the unit with the running CLI's template, leaving t3 service status permanently reporting "needs an update or repair" #18162

Closed as not planned

Description

@cjkihl

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Related but distinct: #12627 (update writes stale launcher protocol, macOS crash-loop) and #17532 (stale launchd PATH after self-update, macOS). This is the systemd unit template changing shape between versions, on Linux.

Area

apps/server

Steps to reproduce

  1. Linux with systemd, T3 background service installed on t3 0.0.45 (stable).
  2. Run t3 update --channel nightly --yes. The update reports success:
    Background service restarted on 0.0.46-nightly.20261011.2955
  3. Run t3 service status.

Expected behavior

t3 service status should report installed · t3@0.0.46-nightly.20261011.2955. The unit on disk should match what the target version renders, since t3 update already rewrote it.

Actual behavior

t3 service status reports needs an update or repair with no [problem] codes listed, and advises Run t3 service install to repair it. This persists indefinitely. The service itself is healthy — it serves HTTP and the managed tunnel reconnects — but every status check misreports.

The unit is missing three directives that the target version's renderer emits:

--- a/~/.config/systemd/user/t3code.service
+++ b/~/.config/systemd/user/t3code.service
@@ -13,6 +13,9 @@
 OOMPolicy=continue
 Restart=always
 RestartSec=5
+CPUWeight=1000
+IOWeight=1000
+ManagedOOMPreference=avoid
 StandardOutput=append:/root/.t3/userdata/logs/boot-service.log
 StandardError=append:/root/.t3/userdata/logs/boot-service.log

ExecStart is correctly repointed at the new version. Everything else status validates passes: problems is empty, .install-complete matches, service-state.json activeVersion matches, and the runtime entry binary exists. The unit-content comparison is the only failing condition:

current: problems.length === 0
  && normalizeUnit(unit) === normalizeUnit(detectedManager.render(plan))
  && runtimeEntryExists
  && isSome(runtimeSentinel) && runtimeSentinel.value.trim() === input.cliVersion
  && state?.activeVersion === input.cliVersion
  && state?.update?.status !== "pending"

Root cause: t3 update performs the service switch in the running (old) CLI's process, and writes the unit through BootService.install(...) with cliVersion: targetVersion:

yield* BootService.pipe(
  flatMap$3((target) => target.install({ allowDowngrade, start: restartService })),
  provide(layer$200({ baseDir, logsDir, cliVersion: targetVersion })),
)

The cliVersion is stamped as the target, but renderBootServiceUnit still comes from the old binary. Comparing the two renderers:

  • 0.0.45 src/cloud/bootService.ts emits no CPUWeight, IOWeight, or ManagedOOMPreference.
  • 0.0.46-nightly.20261011.2955 emits all three.

So any change to the unit template between two versions makes the unit written by t3 update permanently unmatchable by the version it points at. This is the same structural issue as #12627 (old CLI performing the switch) but a distinct code path and symptom — no crash-loop, and the service stays up.

Workaround: run t3 service install once after the update. It re-renders the unit from the target's own template and the status clears. Verified working.

Suggested fix: delegate the unit write to the target version's CLI when the target differs from the running CLI, or re-render via the installed runtime binary rather than the in-process renderer.

Impact

Minor bug or occasional failure

Every user crossing a version boundary that changes the unit template gets a permanently-failing t3 service status, plus a repair prompt for a service that is actually healthy. Anything gating on that status (scripts, health checks, onboarding flows) gets a false "needs repair" reading.

Version or commit

0.0.45 → 0.0.46-nightly.20261011.2955

Environment

Ubuntu 24.04.3 LTS, Linux 6.8.0-139-generic, aarch64, systemd 255, background service installed via t3 service install. T3CODE_HOME=/root/.t3.

Logs or stack traces

$ t3 service status
T3 Code service
  Status: needs an update or repair
  Unit: /root/.config/systemd/user/t3code.service
  Logs: /root/.t3/userdata/logs/boot-service.log
  Next: Run `t3 service install` to repair it.

$ ./t3 update --channel nightly --yes
  Updating T3 Code 0.0.45 → 0.0.46-nightly.20261011.2955 (nightly)
  ...
  Installed T3 Code 0.0.46-nightly.20261011.2955
  Background service restarted on 0.0.46-nightly.20261011.2955

Service stayed healthy throughout — HTTP 200, managed tunnel reconnected on 4 edges, no errors in boot-service.log after the restart.

Activity

  1. juliusmarminge commented on Oct 11, 2026

    @juliusmarminge
    Member

    Note

    Grok responding on behalf of Julius.

    Duplicate of #12627 — same root cause: t3 update runs the service switch in the old CLI process (apps/server/src/cli/update.ts), so the unit is rendered by the running binary while cliVersion is stamped as the target.

    Here the mismatch is the systemd unit template, not SERVICE_LAUNCHER_PROTOCOL. #17662 added CPUWeight / IOWeight / ManagedOOMPreference; 0.0.45's renderer omits them, so after that hop status's unit equality check stays false forever while the service itself is fine.

    Workaround (same as #12627): t3 service install from the new binary. Please track the unit-template case on #12627 — a protocol-only migration would not cover it.

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

    duplicateThis issue or pull request already existsvia-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