Desktop stays windowless for 8+ minutes after an update while startup recovery holds a write transaction open#23372

opened2d agobyRrealhasanshoaib

What happened

After T3 Code (Nightly) installed an update and restarted itself, no window appeared. Clicking the Dock icon did nothing visible. After about 8 minutes the user quit the app, and the next launch showed "This T3 home is unavailable".

Diagnosis

The backend's command-readiness middleware (layerCommandReadiness, apps/server/src/server.ts:683-688) is global, so every HTTP request, including /.well-known/t3/environment, waits for commandGate.signalCommandReady (apps/server/src/serverRuntimeStartup.ts:670). A request that arrives once the listener is up but before that signal hangs with no timeout instead of failing.

In this run startup never reached that signal. Trace spans show server.startup.orchestration-v2.recovery (ProviderRuntimeRecoveryService.reconcile) running for 671 s from 08:29:47. Inside it, one ProviderRuntimeRecoveryService.reconcileProjection → orchestrationV2.EventSink.commitCommand (ProviderRuntimeRecoveryService.ts:666) held a writer sql.transaction open from 08:29:51 to 08:40:49 (658 s). Its traced work was trivial: 6 sqlite statements plus EffectOutbox.cancelUnsettled and EffectOutbox.enqueue, under 0.1 s in total, with db.lock_wait_ms 0.017. The transaction closed within seconds of the backend getting SIGTERM, so it looks stuck on something untraced inside commitCommand rather than on slow SQL. On the next two launches of the same build, the same recovery phase took 4.8 s and 7.0 s, and all of server.startup took 6.6 s and 9.8 s.

The desktop gives no sign of any of this. It creates the main window only once the backend is ready (createMainIfBackendReady and waitingForBackend, apps/desktop/src/window/DesktopWindow.ts:884-895). The connecting splash is shown only for WSL-only setups (apps/desktop/src/app/DesktopApp.ts:242-244), and onReadinessFailure only logs a warning and saves a snapshot (apps/desktop/src/backend/DesktopBackendManager.ts:934-944). So the probe loop retries forever behind no window.

None of the 32 commits on main after this build (b5a226e4..3b95e783) touch these paths.

Steps to reproduce

Not reproduced on demand. A fresh launch of the same build a few minutes later passed readiness in about 10 s. The occurrence was:

  1. Run the macOS desktop app with Local environment on and a large state database (statev2.sqlite is 17 GB here).
  2. Let the desktop install the nightly update and restart itself.
  3. The new backend logs "Listening", startup recovery holds a write transaction open, GET /.well-known/t3/environment never answers, and the desktop keeps retrying with no window.

Version

0.1.2-nightly.20261021.3499 (desktop app and its bundled backend)

Environment

macOS 26.6.2, MacBook Air M4 (16 GB), desktop app, Local environment on, no background service installed

Evidence

shell
1server-child.log, local time UTC+4
208:29:45 backend pid 65232 starts (update restart marker present)
308:29:46 WARN Database migration history diverges from this build; recorded migration ids are skipped, not reconciled by name.
408:29:46 INFO Listening on http://127.0.0.1:3773
508:30:45 Timed out after 60000ms waiting for desktop backend readiness at http://127.0.0.1:3773/.well-known/t3/environment
6... the same timeout every 60 s, 8 times in total, through 08:37:45
708:37:44-57 four desktop.lifecycle.activate / desktop.window.createMainIfBackendReady spans (Dock clicks), no window
808:37:58 desktop quits; pid 65232 survives with PPID 1, holding server-owner.sqlite
908:39:32 relaunch: new backend exits 78, ServerAlreadyRunningError
10
11server.trace.ndjson, same run
1208:29:47 server.startup.orchestration-v2.recovery 671 s Success
1308:29:51 ProviderRuntimeRecoveryService.reconcileProjection 658 s
1408:29:51 orchestrationV2.EventSink.commitCommand 658 s
1508:29:51 sql.transaction (writer, db.lock_wait_ms 0.017) 658 s children: 6x sqlite + 2 EffectOutbox calls, <0.1 s total
16
17event-loop-stalls.ndjson in that window: max delay about 3 s, so the process was not frozen.

Related issues

#5364 (closed): the same "headless after the 60 s deadline" symptom on Windows, where the backend did become ready later; here it never answered. #22983: the leftover backend after quit, which is what turned this into "This T3 home is unavailable"; evidence for that part is in a comment there.

Fix applied or workaround

Sent SIGTERM to the leftover backend (it took about 60 s to exit), then reopened the app.

Filed by

claude (claude-opus-5-5) via Claude Code, following the t3 triage playbook

Synced from GitHub

Note

Claude Opus 5.5 in Claude Code, writing on behalf of @tsmith165. Measured on his Mac on 2026-10-22.

A similar sequence on macOS after the 0.1.2-nightly.20261022.3526 update restart, with a 44.2 GiB statev2.sqlite holding 6.6M events:

  • 06:21 to 06:27 local: server-child.log shows the desktop's readiness probe (/.well-known/t3/environment) timing out after 60000 ms seven times in a row, all for the same backend pid.
  • 06:47:30, 06:47:42 and 06:48:09: the desktop started three new backends. Each exited with code 78 and ServerAlreadyRunningError, because the first backend still owned the T3 home.
  • 06:48:35: a fourth start succeeded.

On the previous nightly (20261021, 06:36), the same probe timed out once, and a 19 s event-loop stall was logged in that minute.

I have no startup spans from this run, so I can't confirm that the startup recovery transaction caused it. The event-loop stall log shows 13 s stalls throughout the hour after startup, which would also delay the probe.