Skip to content

fix(server): the service no longer crash-loops after t3 update crosses a launcher protocol - #12629

Open
Mnigos wants to merge 4 commits into
pingdotgg:mainfrom
Mnigos:launcher-adopts-older-install-state
Open

Mnigos wants to merge 4 commits into
pingdotgg:mainfrom
Mnigos:launcher-adopts-older-install-state

Conversation

@Mnigos

@Mnigos Mnigos commented Sep 19, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #12627.

Problem

t3 update runs the service switch in the outgoing CLI's process, so install() writes runtime/service-state.json with that CLI's compile-time protocol next to the new version:

{ "protocol": 2, "activeVersion": "0.0.43-nightly.20260919.1962" }

The unit already points at the new launcher, which accepts only its own protocol, throws Service state is invalid or unsupported., and gets respawned by launchd/systemd every 5 s while the update reports success. Hosts that updated from 0.0.42 stay down until someone runs t3 service install on the machine.

Fix

This is the first option from the triage: the launcher accepts and migrates compatible older state.

  • adoptOlderInstallState in serviceProtocol.ts reads the two-field document an install writes when it is stamped with an older protocol, and returns it under the current one.
  • readServiceState in the launcher falls back to it and rewrites the file durably, so t3 service status (which uses the strict decoder) reads the state too. This also unsticks hosts that are crash-looping right now, as soon as they get a launcher with the fix.

The protocol guards the runtime layout, and the launcher still verifies that itself (runtimeExists) before starting a child, so adopting the document does not skip that check. Still rejected as before: a document with an update record (that belongs to an older launcher's update flow), a newer protocol, and a malformed protocol or version. The remote preflight stays strict.

Not included: making t3 update refuse or delegate when protocols differ. It cannot help the 0.0.42 CLIs already released, and it is a separate change to the update command.

Tests

serviceLauncher.test.ts gains three groups, all on temp directories with the existing fake-runtime fixture:

  • The exact document from the issue (and a protocol 1 one) is returned under the current protocol, and the file on disk then parses with the strict decoder.
  • A Launcher built from the adopted state starts the active child for that version.
  • A pending or committed update record under protocol 2, protocol 4, 0, -1, "2", 2.5, an invalid version, and malformed JSON still throw and leave the file byte-for-byte unchanged.

With the adoption disabled the first two groups fail with Service state is invalid or unsupported.. The launcher, preflight, and boot-service suites pass (62 tests); targeted lint and the server typecheck are clean. No installed service or real T3 home was touched.

Implemented with Claude Code (Claude Fable 5.1); tests written with Codex (GPT-6 Astra).

Summary by CodeRabbit

  • Bug Fixes
    • Existing installations using supported older service-state formats are now recognized and upgraded automatically, allowing services to start normally.
    • Legacy service states using supported protocol versions are saved in the current format for future launches.
    • Invalid, unsupported, malformed, or future-format service states—including states with pending updates—are rejected with a clear error.
    • Rejected state files remain unchanged, helping preserve recovery information.

…s a launcher protocol

`t3 update` installs the service from the outgoing CLI, so the state file
carries that CLI's protocol next to the new version. The new launcher
rejected the document and the service manager respawned it every few
seconds while the update reported success.

The launcher now adopts the two-field document an older install writes and
rewrites it under its own protocol, so `t3 service status` reads it as well.
The runtime layout the protocol stands for is still checked before a child
starts. A document with an update record, a newer protocol, or a malformed
protocol is rejected as before.
@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Sep 19, 2026
@coderabbitai

coderabbitai Bot commented Sep 19, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: pingdotgg/t3code/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: c3f6dde5-d413-4e64-ae23-80797fcc6b64

📥 Commits

Reviewing files that changed from the base of the PR and between 8abc60b and d39aba2.

📒 Files selected for processing (1)
  • apps/server/src/cloud/serviceProtocol.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

The service launcher adopts compatible older install-state protocols, rewrites them with the current protocol, and starts the active runtime. Tests cover legacy protocols, invalid states, malformed JSON, persistence, and runtime startup.

Changes

Service state compatibility

Layer / File(s) Summary
Legacy state parser
apps/server/src/cloud/serviceProtocol.ts
Adds adoptOlderInstallState, which validates compatible install-state documents and normalizes their protocol.
Launcher migration and validation
apps/server/src/serviceLauncher.ts, apps/server/src/serviceLauncher.test.ts
readServiceState persists adopted state before returning it. Tests cover protocols 1 and 2, runtime startup, rejected states, malformed JSON, and unchanged files after rejection.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~15 minutes

Change: Bug fix · Severity of issue fixed: Medium

Suggested reviewers: t3dotgg

Merge Risk: ⚪ Minimal · up to d39ab

The launcher migrates the compatible legacy install state before starting the service. No actionable merge blocker is established.

Security Architecture Review

Security architecture risk: 🔵 Low · up to d39ab

The compatibility change retains version validation and runtime checks, with no demonstrated expansion of remote authority. A remaining concurrency risk is that startup normalization could overwrite a newer installation state if installation and launcher startup overlap.

Retained concerns

  • Low · reliability · inferred: If launcher startup overlaps an installation that skips stopping the service, normalization can replace a newer activeVersion with the previously read legacy value. Atomic rename prevents partial files, not stale read-modify-write replacement. This can leave persisted runtime selection inconsistent with the newly installed unit and undermine update recovery. Normal stop-before-write installation reduces exposure; actual deployment overlap is not established.
Security review details

Security Blast Radius

  • inferred — The directly affected scope is the service-state file and runtime selected beneath one configured T3CODE_HOME. Filesystem ownership and deployment permissions were not established, so broader local attacker reachability cannot be quantified.

Security Findings and Attack Paths

  • inferred — No new remote attack path was demonstrated. A party already able to replace the state file could submit a valid current-protocol document before this change; accepting equivalent legacy documents does not by itself grant additional runtime-selection authority. This conclusion does not establish filesystem boundary safety.

Trust Boundaries and Controls

  • observed — Persisted JSON passes through current state validation before becoming launcher state. Runtime selection then requires an executable file and matching installation sentinel. These are completeness checks, not evidence of cryptographic runtime authenticity; the checks remain unchanged by adoption.

Resilience and Maintainability Implications

  • inferred — Durable replacement contains partial-file failures, but does not preserve freshness across independent writers. Installer stop-before-write sequencing is the strongest counterevidence to the lost-update concern; its deliberately non-stopping installation option leaves concurrency containment dependent on operational sequencing.

Hardening Proposals

  • proposed — If startup and installation can overlap, coordinate all state producers with a shared serialization mechanism or enforce an exclusive migration window. Validate that an interleaved installation cannot be replaced by a stale normalized snapshot.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 5 functions across 3 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main fix: preventing service crash loops when an update crosses launcher protocol versions.
Description check ✅ Passed The description clearly explains the problem, the migration-based fix, scope limits, and test coverage. It omits the template headings and checklist, but it provides the required change rationale and …
Linked Issues check ✅ Passed The PR satisfies the coding requirements in [#12627]. adoptOlderInstallState accepts compatible protocol 1 and 2 documents without update records, rewrites them with the current protocol, and valida…
Out of Scope Changes check ✅ Passed The changes stay within [#12627]. Production changes implement legacy service-state adoption and durable migration. Tests cover the migration and strict rejection behavior. No unrelated production beh…
  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR

Comment @coderabbitai help to get the list of available commands.

macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Sep 19, 2026
@macroscopeapp

macroscopeapp Bot commented Sep 19, 2026 •

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved at d39aba2

Macroscope's review found this PR approvable — This is a focused compatibility fix for legacy service-state files: only known protocol 1/2 install records are migrated, while malformed, future, or update-in-progress records remain rejected. Existing state handling and runtime validation remain unchanged, with targeted tests covering migration and startup.

You can add or adjust custom eligibility rules. Learn more.

The Effect diagnostic rejects a bare JSON.stringify in the typecheck; the
file already opts out per call site for fixtures, so the new table does too.
… need

The helper takes an unknown value, so the JSON diagnostic never fires there
and the unused directive is itself a typecheck warning that fails CI.

@kvnloo kvnloo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One protocol-compatibility invariant worth tightening.

Comment thread apps/server/src/cloud/serviceProtocol.ts Outdated
@juliusmarminge juliusmarminge added the macroscope-review Opt PRs made by unvouched contributors in for Macroscope review. Vouched contributors auto-reviews label Oct 1, 2026 — with ChatGPT Codex Connector
@macroscopeapp
macroscopeapp Bot dismissed their stale review October 1, 2026 07:28

Dismissing prior approval to re-evaluate d39aba2

This branch has not been deployed

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

Labels

macroscope-review Opt PRs made by unvouched contributors in for Macroscope review. Vouched contributors auto-reviews size:M 30-99 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

3 participants