{"record":{"id":"e273ce52965f7b01","repo":"nexu-io/open-design","slug":"open-design-was-launched-headlessly-but-its-daemon","errorCode":null,"errorMessage":"Open Design was launched headlessly but its daemon did not become ready within ${timeoutMs}ms.","messagePattern":"Open Design was launched headlessly but its daemon did not become ready within (.+?)ms\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/daemon/src/mcp-bootstrap.ts","lineNumber":147,"sourceCode":"    throw new Error(\n      `The registered Open Design runtime is unavailable and cannot be launched (${plan.reason}).`,\n    );\n  }\n\n  await spawnBootstrap(plan);\n  const deadline = Date.now() + timeoutMs;\n  while (Date.now() < deadline) {\n    await sleep(DEFAULT_BOOTSTRAP_POLL_MS);\n    daemonUrl = registeredBootstrapTarget\n      ? await discoverTargetDaemonUrl(env, 300)\n      : await resolveDaemonUrl({\n          env,\n          flagUrl: null,\n          timeoutMs: 300,\n        });\n    if (daemonUrl != null && await probeDaemon(daemonUrl)) return daemonUrl;\n  }\n  throw new Error(\n    `Open Design was launched headlessly but its daemon did not become ready within ${timeoutMs}ms.`,\n  );\n}\n\nasync function discoverDaemonUrlFromRegisteredIpc(\n  env: NodeJS.ProcessEnv,\n  timeoutMs: number,\n): Promise<string | null> {\n  const socketPath = env[SIDECAR_ENV.IPC_PATH];\n  if (socketPath == null || socketPath.length === 0) return null;\n  try {\n    const status = await requestJsonIpc<DaemonStatusSnapshot>(\n      socketPath,\n      { type: SIDECAR_MESSAGES.STATUS },\n      { timeoutMs },\n    );\n    return status.url;\n  } catch {","sourceCodeStart":129,"sourceCodeEnd":165,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/apps/daemon/src/mcp-bootstrap.ts#L129-L165","documentation":"Thrown by ensureMcpDaemonUrl() after it successfully spawned the headless bootstrap process but the daemon failed to become healthy within timeoutMs (default DEFAULT_BOOTSTRAP_TIMEOUT_MS). The loop polls discoverTargetDaemonUrl/resolveDaemonUrl and probeDaemon every DEFAULT_BOOTSTRAP_POLL_MS until the deadline; if none of the probes succeed, the spawned daemon is presumed stuck or crashed.","triggerScenarios":"The bootstrap command ran but the daemon exited early, never bound its port, or its /api/health endpoint never returned 200. Commonly: port already in use, missing/broken native module (better-sqlite3 not built for this Node), the daemon hit a fatal error during startup, the IPC socket path is unreachable, or the machine is too slow to boot within the timeout.","commonSituations":"Port conflict on the daemon's expected port; better-sqlite3 native binary missing for the running Node version (especially Windows — see repo AGENTS.md Windows notes); daemon data dir permissions issue; CI environment with slow disk; OD_DAEMON_URL in env points somewhere the daemon did not bind.","solutions":["Check the daemon's log output (it is spawned detached — look in the packaged log dir or run the daemon in the foreground to see the startup error).","Free the port the daemon tries to bind, or pick a free one and set it consistently.","Rebuild native modules for the active Node version (`pnpm install` / `pnpm rebuild better-sqlite3`).","Increase timeoutMs in ensureMcpDaemonUrl options if the machine is legitimately slow.","Verify the daemon data directory is writable and not corrupted."],"exampleFix":"# before\nensureMcpDaemonUrl(); // default timeout, daemon crashes silently\n\n# after — foreground daemon to see the error, then retry\npnpm tools-dev   # observe startup errors here\nensureMcpDaemonUrl({ timeoutMs: 30000 });","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"import { ensureMcpDaemonUrl } from './mcp-bootstrap';\n\nasync function ensureDaemonWithDiagnostic(): Promise<string> {\n  try {\n    return await ensureMcpDaemonUrl({ timeoutMs: 30000 });\n  } catch (error) {\n    if (error instanceof Error && error.message.includes('did not become ready')) {\n      // Spawned but unhealthy: surface a pointer to the daemon log and bail.\n      throw new Error('Daemon launched but did not become healthy. Inspect the daemon log and port usage, then retry.');\n    }\n    throw error;\n  }\n}","preventionTips":["Run the daemon in the foreground first to confirm it boots cleanly before relying on headless bootstrap.","Rebuild native modules (better-sqlite3) for the active Node version after every Node upgrade.","Keep the daemon's port free; pick a deterministic free port and set it consistently.","Allow a longer timeoutMs on slow/CI machines."],"tags":["mcp","bootstrap","daemon","timeout","launch","native-modules"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}