fix(host-service): harden DB migration startup (busy_timeout + fail-closed)
Problem
The host-service runs DB migrations synchronously inside createApp() before the port binds (app.ts → serve.ts). The DB was opened with only WAL + foreign_keys (no busy_timeout), and createDb swallowed any migration error and returned the db anyway. desktop 1.12.0 added the first migration since this code shipped (0005_host_settings_and_project_overrides), exposing two failure modes under write-lock contention on host.db:
- Half-migrated, served anyway —
SQLITE_BUSYswallowed → runtimeno such table host_settings. - Killed mid-migration — the synchronous stall exceeds the coordinator's 10s health window → SIGKILL → respawn loop, port never binds (terminals dead / 'can't type').
Fix
db/db.ts— setbusy_timeout = 8000(wait out transient contention) and fail closed: throw instead of swallowing. drizzle runs all pending migrations in a singleBEGIN/COMMITwithROLLBACK+rethrow, so a failure leaves the DB at its prior version (never half-applied). The throw propagates toserve.tsmain().catch(… process.exit(1)), so the coordinator's health check fails instead of serving a broken DB.host-service-utils.ts— raiseHEALTH_POLL_TIMEOUT_MS10s → 20s so a recoverable ~8s migration stall isn't SIGKILLed before the port binds.
Reproduction + verification
New integration test db.contention.node-test.ts: real createDb + real 0005 + a sibling process holding the write lock. Run with bun run test:integration:db (Electron-as-node — better-sqlite3 here is built for the Electron ABI; bun can't load it and plain node is the wrong ABI).
- ✔ baseline: no contention → 0005 applies,
host_settingsexists - ✔ fail-closed: sustained contention →
createDbthrows, DB left cleanly unmigrated - ✔ recovery: contention clears within the timeout → waits then succeeds
Host-service unit suite 691/0, typecheck + lint clean.
Scope / follow-up
This hardens migration startup (resilience + correctness). It does not address the source of the contention — whether two host-services can race the same host.db — which is a separate, root-cause lifecycle change (reap orphan host-service before spawn / single-writer guarantee) tracked in a follow-up PR. The plan doc (plans/host-service-migration-startup-hardening.md) covers both.
Summary by cubic
Hardened host-service DB migration at startup by failing closed so we never serve a half-migrated DB. Removed the migration busy_timeout and focused on surfacing failures; added an Electron-as-node integration test for write-lock contention.
-
Bug Fixes
src/db/db.ts: removed the try/catch that swallowed migration errors;migrate()now throws. drizzle keeps migrations atomic, so failures roll back cleanly. Dropped the migrationbusy_timeout.
-
Tests
src/db/db.contention.node-test.ts: integration test holds a write lock in a sibling process and asserts fail-closed behavior.scripts/run-db-integration-test.ts+test:integration:db: run under Electron-as-node to loadbetter-sqlite3.
Summary by CodeRabbit
-
Bug Fixes
- Improved database migration error handling for host service to ensure the application fails cleanly at startup when database contention is detected, rather than silently continuing with an unmigrated database.
-
Tests
- Added integration test suite to verify database behavior and recovery under write-lock contention scenarios.