# Desktop stays windowless for 8\+ minutes after an update while startup recovery holds a write transaction open · Issue \#23372 · pingdotgg/t3code

[View on GitCafe](https://git.cafe/pingdotgg/t3code/issues/23372)

Repository: [pingdotgg/t3code](https://git.cafe/pingdotgg/t3code)

Visibility: public

State: backlog

Priority: 0

## Description

[Discussion and activity](https://git.cafe/pingdotgg/t3code/issues/23372)

---

### 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
server-child.log, local time UTC+4
08:29:45  backend pid 65232 starts (update restart marker present)
08:29:46  WARN Database migration history diverges from this build; recorded migration ids are skipped, not reconciled by name.
08:29:46  INFO Listening on http://127.0.0.1:3773
08:30:45  Timed out after 60000ms waiting for desktop backend readiness at http://127.0.0.1:3773/.well-known/t3/environment
...       the same timeout every 60 s, 8 times in total, through 08:37:45
08:37:44-57  four desktop.lifecycle.activate / desktop.window.createMainIfBackendReady spans (Dock clicks), no window
08:37:58  desktop quits; pid 65232 survives with PPID 1, holding server-owner.sqlite
08:39:32  relaunch: new backend exits 78, ServerAlreadyRunningError

server.trace.ndjson, same run
08:29:47  server.startup.orchestration-v2.recovery                 671 s  Success
08:29:51  ProviderRuntimeRecoveryService.reconcileProjection       658 s
08:29:51  orchestrationV2.EventSink.commitCommand                  658 s
08:29:51  sql.transaction (writer, db.lock_wait_ms 0.017)          658 s  children: 6x sqlite + 2 EffectOutbox calls, <0.1 s total

event-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

