From 35c92d8314bc68b7ed42aa6cf24d04a52269aeeb Mon Sep 17 00:00:00 2001 From: "Joel Teply (Windows ce)" Date: Mon, 4 May 2026 15:48:10 -0500 Subject: [PATCH] fix(seed): await seedDatabase before SERVER_READY (closes Room-not-found race) carl-install-smoke intermittently failed with "Room not found: general" on the rerun for #1038 (run 25332249956 job 74271087853). Probe landed 14-21s after install completion, but seed was kicked off via setTimeout(3000) in the orchestrator AND setTimeout(5000) in docker-entrypoint -- both fire-and-forget, so SERVER_READY / main() returned while rooms didn't exist yet, and chat/send threw before seed landed. Fix: await seedDatabase() inside SystemOrchestrator before completing SERVER_READY, and drop the duplicate setTimeout in docker-entrypoint. By the time anything downstream sees SERVER_READY (or the container's node-server PID is alive past main()), rooms+personas+recipes are in the DB and resolveRoomIdentifier("general") returns hit. This also removes the duplicate-seed race where two parallel setTimeouts could both call findOrCreateRoom on the same uniqueId before the first DataCreate landed. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/server/docker-entrypoint.ts | 21 +++--------- .../orchestration/SystemOrchestrator.ts | 34 +++++++++---------- 2 files changed, 21 insertions(+), 34 deletions(-) diff --git a/src/server/docker-entrypoint.ts b/src/server/docker-entrypoint.ts index ebcd99bcd..31ad70b1f 100644 --- a/src/server/docker-entrypoint.ts +++ b/src/server/docker-entrypoint.ts @@ -31,23 +31,10 @@ async function main(): Promise { console.log(`✅ Server ready (milestones: ${result.completedMilestones.join(' → ')})`); - // Auto-seed database if empty (first run). - // In-process via Commands.execute() — zero subprocess spawns. - // ~200MB instead of 2GB, <5 seconds instead of 30+. - setTimeout(async () => { - try { - const { seedDatabase } = await import('./seed-in-process'); - const seeded = await seedDatabase(); - if (seeded) { - console.log('✅ Database seeded'); - } else { - console.log('✅ Database already seeded'); - } - } catch (e: unknown) { - const msg = e instanceof Error ? e.message : String(e); - console.warn(`⚠️ Auto-seed: ${msg}`); - } - }, 5000); + // Seed runs synchronously inside SystemOrchestrator before SERVER_READY + // milestone fires (see SystemOrchestrator.ts). No duplicate seed here — + // the previous setTimeout(5000) raced the orchestrator's setTimeout(3000) + // and could re-enter findOrCreateRoom on a partially-committed table. // Keep process alive — server event loop runs in background } diff --git a/src/system/orchestration/SystemOrchestrator.ts b/src/system/orchestration/SystemOrchestrator.ts index 1b6e58349..99158cff4 100644 --- a/src/system/orchestration/SystemOrchestrator.ts +++ b/src/system/orchestration/SystemOrchestrator.ts @@ -1110,24 +1110,24 @@ export class SystemOrchestrator extends EventEmitter { console.debug('✅ Server is ready'); - // Auto-seed database if empty (first run or after data:clear). - // In-process via Commands.execute() — zero subprocess spawns, works in both - // Docker and bare metal. The old npm run data:seed approach spawns jtag CLI - // subprocesses that connect via WebSocket, which is fragile and slow. - setTimeout(async () => { - try { - const { seedDatabase } = await import('../../server/seed-in-process'); - const seeded = await seedDatabase(); - if (seeded) { - console.log('✅ Database seeded (in-process)'); - } else { - console.log('✅ Database already seeded'); - } - } catch (e: unknown) { - const msg = e instanceof Error ? e.message : String(e); - console.warn(`⚠️ Auto-seed failed: ${msg}`); + // Auto-seed database if empty BEFORE declaring SERVER_READY. + // Was setTimeout(3000) → fired-and-forget; orchestrator returned ready + // while seed was still running. carl-install-smoke probed chat/send 7-21s + // after install completed and intermittently hit "Room not found: general" + // because rooms hadn't landed yet. Awaiting seed here closes that race — + // by the time downstream sees SERVER_READY, rooms+personas exist. + try { + const { seedDatabase } = await import('../../server/seed-in-process'); + const seeded = await seedDatabase(); + if (seeded) { + console.log('✅ Database seeded (in-process)'); + } else { + console.log('✅ Database already seeded'); } - }, 3000); + } catch (e: unknown) { + const msg = e instanceof Error ? e.message : String(e); + console.warn(`⚠️ Auto-seed failed: ${msg}`); + } await milestoneEmitter.completeMilestone( SYSTEM_MILESTONES.SERVER_READY,