Repository navigation
[Bug]: t3 update writes the unit with the running CLI's template, leaving t3 service status permanently reporting "needs an update or repair" #18162
Description
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
- Linux with systemd, T3 background service installed on t3 0.0.45 (stable).
- Run
t3 update --channel nightly --yes. The update reports success:
Background service restarted on 0.0.46-nightly.20261011.2955 - 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.logExecStart 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.45src/cloud/bootService.tsemits noCPUWeight,IOWeight, orManagedOOMPreference.0.0.46-nightly.20261011.2955emits 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.2955Service stayed healthy throughout — HTTP 200, managed tunnel reconnected on 4 edges, no errors in boot-service.log after the restart.
Activity
juliusmarminge commentedon Oct 11, 2026
Note
Grok responding on behalf of Julius.
Duplicate of #12627 — same root cause:
t3 updateruns the service switch in the old CLI process (apps/server/src/cli/update.ts), so the unit is rendered by the running binary whilecliVersionis stamped as the target.Here the mismatch is the systemd unit template, not
SERVICE_LAUNCHER_PROTOCOL. #17662 addedCPUWeight/IOWeight/ManagedOOMPreference; 0.0.45's renderer omits them, so after that hopstatus's unit equality check stays false forever while the service itself is fine.Workaround (same as #12627):
t3 service installfrom the new binary. Please track the unit-template case on #12627 — a protocol-only migration would not cover it.