Skip to content

[Bug]: background service self-update fails permanently against a pre-#11510 service-launcher.mjs #11934

Description

@quinnypig

Area

apps/server

Summary

In-app self-update never rewrites ~/.t3/runtime/service-launcher.mjs, so every host running the background service is still using whichever launcher its last CLI-driven install wrote. #11510 changed how the launcher resolves a runtime's entry point, and #11607 changed what the installed runtime tree contains — together they make those persisted launchers reject every new release.

The compatibility gate for exactly this case already exists (runServicePreflight blocks on a launcherProtocol mismatch with "This release requires a newer T3 Code service launcher"), but SERVICE_LAUNCHER_PROTOCOL was left at 2 through #11510, so it never fires. The user gets a misleading error instead.

Steps to reproduce

  1. Install the background service on Linux on a build predating feat(server): manage runtimes as release archives only, never from npm #11510 (my host's service-launcher.mjs was written 2026-08-10) and let it self-update through the app for a few weeks. The launcher file is never replaced.
  2. Let the server reach 0.0.41-nightly.20260914.1722 — the last release whose runtime archive still contained node_modules/t3/dist/bin.mjs.
  3. From any client, run Update server to 0.0.41-nightly.20260915.1735 or newer.

Expected behavior

Either the update lands, or it is blocked up front with the actionable message that servicePreflight.ts already contains: "This release requires a newer T3 Code service launcher. Update it on the server machine."

Actual behavior

The update fails, identically and permanently, on every attempt:

  • The new runtime downloads and installs completely, sentinel and all.
  • The server executes the staged binary's preflight, which returns ready.
  • The launcher then rejects the handoff, and the app shows: Server update failed: The requested target runtime is missing or incomplete.

That message is wrong in a way that costs debugging time: the target runtime is complete and was just successfully executed by the same code path. What is actually stale is the launcher.

The mechanism:

0.0.41-nightly.20260914.1722 shipped both layouts, which is why it was the last version to install successfully.

Impact

Major degradation or frequent failure

Silent and self-perpetuating: the update looks like it is working (the version installs fully), the error suggests a corrupt download, and nothing points at the real fix. It should hit any background-service host whose launcher predates #11510, which is effectively all of them, since in-app self-update never replaces that file.

Version or commit

Server stuck on 0.0.41-nightly.20260914.1722; failing to reach 0.0.41-nightly.20260915.1735 and .1766. Analysis against main @ 7235701.

Environment

Ubuntu 24.04 arm64, systemd user service, nightly channel, headless (no desktop app).

Logs or stack traces

# ~/.t3/userdata/logs/server.trace.ndjson, span cloud.server_self_update.update
ServerSelfUpdateError: Server update failed: The requested target runtime is missing or incomplete.
    at failWith (.../versions/0.0.41-nightly.20260914.1722/t3:87571:97)
    at cloud.server_self_update.update (.../versions/0.0.41-nightly.20260914.1722/t3:87533:40) {
  [cause]: ServiceLauncherRejectedError: The requested target runtime is missing or incomplete.
}

# state after two failed attempts: new runtimes fully installed, never activated
$ ls ~/.t3/runtime/versions/
0.0.41-nightly.20260914.1722   <- active
0.0.41-nightly.20260915.1735   <- installed, valid .install-complete, never used
0.0.41-nightly.20260915.1766   <- installed, valid .install-complete, never used

$ cat ~/.t3/runtime/service-state.json
{"protocol":2,"activeVersion":"0.0.41-nightly.20260914.1722","update":{...,"status":"committed"}}
# untouched since the .1722 update; the launcher never records a pending update

$ ls -l ~/.t3/runtime/service-launcher.mjs
-rw-rw-r-- 1 ... 21382 Aug 10 17:39   # never rewritten by ~25 successful self-updates
$ grep -n 'entryPath' ~/.t3/runtime/service-launcher.mjs
116: entryPath: NodePath.join(versionDir, "node_modules", "t3", "dist", "bin.mjs"),

Note: ~/.t3/userdata/logs/boot-service.log had been deleted on disk while systemd still held the fd, so the launcher-side log was only reachable via /proc/<launcher-pid>/fd/1. Possibly worth a separate look — nothing in the repo appears to unlink it.

Workaround

Run t3 update on the server machine from a current on-disk binary. This rewrites the unit to ExecStart=<versionDir>/t3 __service-launcher, which hosts the launcher inside the versioned executable and fixes it permanently:

~/.t3/runtime/versions/<newest>/t3 update --channel nightly --yes

Confirmed working — the service came back on .1766, with drop-ins and Tailscale Serve intact.

Suggested fix

Bump SERVICE_LAUNCHER_PROTOCOL to 3. The preflight then blocks before anything downloads and emits the correct message. Worth considering alongside it: any layout change to runtimePaths is a launcher compatibility break by definition, since that file outlives every self-update, so a comment on the constant tying the two together would help the next person.

Activity

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

    acceptedfeature request acceptedbugSomething 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