Desktop app fails to start with code=1 when a stored thread has a boolean settledOverride#28279

opened11d agobyCcpugenius

What happened

After updating to 0.1.6 via the built-in updater, the desktop app shows "T3 Code could not start (code=1)". Reinstalling 0.1.5 shows the same error. The dialog's stack trace goes through ProjectionStore.getRuntimeRecoveryProjection and ProviderRuntimeRecoveryService.reconcile during server startup.

Expected: the server starts and recovers thread state. Actual: startup fails with ProjectionStoreReadError: "Failed to read orchestration projection for thread ", caused by SchemaError: Expected "settled" | "active" | null | undefined at ["settledOverride"].

Diagnosis

Startup reads every row in orchestration_v2_projection_threads. One row had payload_json with "settledOverride":true (a JSON boolean). The schema in packages/contracts/src/orchestrationV2.ts accepts only Literals ["settled", "active"] (nullable), so decoding that row throws, and startup exits with code 1. Only one row in the database had a boolean; the rest were "settled" (218), null (274) or absent.

The same schema exists in v0.1.5 (packages/contracts/src/orchestrationV2.ts), so rolling back does not recover startup. Migrations 73-76 ran on the same database during the 0.1.6 start.

I could not find which build or client wrote the boolean. No source writer in v0.1.5 or v0.1.6 produces settledOverride: true, and the event tables do not contain it, only the projection row.

Steps to reproduce

Not confirmed as a deterministic repro. Known path:

  1. Have a thread whose projection row contains "settledOverride":true (cause unknown; it came from a thread created via the MCP creation source and was later settled).
  2. Start the desktop app with that database. Startup fails with code=1.

Workaround in the workaround field.

Version

0.1.6 (failed to start). Also reproduced on 0.1.5 with the same database.

Environment

macOS 27.0.0 (Darwin), arm64. Node v26.12.1. Desktop app (Beta) launched from /Applications. t3 triage CLI 0.1.6. Server runs as the desktop backend child process.

Evidence

shell
1- Desktop backend log: crash repeated across several runs. The final error is the ProjectionStoreReadError / SchemaError chain.
2- Migrations 73-76 applied at 14:13 local on the 0.1.6 start.
3- Row check (read-only): one boolean settledOverride in 493 thread payloads; zero in orchestration_v2_events.
4- Backup taken before the fix; checksum verified.

Searched pingdotgg/t3code for settledOverride, ProjectionStore, and 'Provider runtime recovery': no matching open issue. Related but different: #17322 (closed), #13544 (closed).

Fix applied or workaround

On a machine that cannot start, back up ~/.t3/userdata/statev2.sqlite, then in orchestration_v2_projection_threads change "settledOverride":true to "settledOverride":"settled" in the affected row's payload_json. This is a manual database edit; take a backup first.

Filed by

Claude Haiku 5.5 (claude-haiku-5-5), via Claude Code, during t3 triage.

Taking this one. I've reproduced the failure mechanism from the code and have a fix in progress.

What I verified

  • The crash path is ProviderRuntimeRecoveryService.reconcile at startup: it lists recovery-visited threads and decodes each thread's payload_json through OrchestrationV2AppThreadJson. settledOverride only accepts "settled" | "active" | null, so one row storing a JSON boolean fails decode, the error escapes as a fatal ProviderRuntimeRecoveryError, and the server exits code=1 — on every launch, and on 0.1.5 too, since the schema is identical there. No duplicate issue; #16441 is a different read path (turn start).
  • On the writer: every writer in the current code and the full git history emits literals, encode-side validation rejects booleans, and the event tables never carried one — consistent with your row check. The repo has documented precedent that older/foreign builds share and write the same database (see 900652d817, "sweep index follows payloads written by any build", and #15948's repair migration), so the boolean most plausibly came from a build outside this history.

The fix direction

Make settledOverride decode leniently in the contracts schema, following the existing posture for stored payloads written by any build (LenientThreadOpens, ForwardCompatibleNullable): true decodes as "settled", false as null. Encode stays strict, so the next write of the thread normalizes the stored value back to a literal — no manual database edit needed. This heals reads everywhere the schema is shared: startup recovery, sidebar lists, and the client thread shells.

Fix is up: #28335. The contracts schema now decodes a boolean settledOverride (true → "settled", false → null), so startup recovery reads the poisoned row instead of exiting code=1, and the value normalizes back to a literal on the thread's next write — no manual database edit needed.