{"record":{"id":"25ade9eb5ecd3453","repo":"paperclipai/paperclip","slug":"database-unavailable","errorCode":null,"errorMessage":"database_unavailable","messagePattern":"database_unavailable","errorType":"http","errorClass":null,"httpStatus":503,"severity":"error","filePath":"server/src/routes/health.ts","lineNumber":161,"sourceCode":"    }\n\n    const persistedDevServerStatus = readPersistedDevServerStatus();\n    if (!persistedDevServerStatus) {\n      res.status(404).json({ error: \"dev_server_supervisor_unavailable\" });\n      return;\n    }\n\n    const restartRequired =\n      persistedDevServerStatus.dirty ||\n      persistedDevServerStatus.changedPathCount > 0 ||\n      persistedDevServerStatus.pendingMigrations.length > 0;\n    if (!restartRequired) {\n      res.status(409).json({ error: \"restart_not_required\" });\n      return;\n    }\n\n    if (!db) {\n      res.status(503).json({ error: \"database_unavailable\" });\n      return;\n    }\n\n    const requestId = randomUUID();\n    const requestedAt = new Date();\n    const serverInfo = opts.serverInfo ?? getServerInfoSnapshot();\n    const preflightActiveRunIds = await db\n      .select({ id: heartbeatRuns.id })\n      .from(heartbeatRuns)\n      .where(eq(heartbeatRuns.status, \"running\"))\n      .then((rows) => rows.map((row) => row.id));\n    let intent: Awaited<ReturnType<typeof writeHotRestartIntent>> | null = null;\n    try {\n      intent = await writeHotRestartIntent({\n        previousServerPid: process.pid,\n        previousServerIdentity: serverInfo.processStartedAt,\n        previousServerVersion: serverVersion,\n        preflightActiveRunIds,","sourceCodeStart":143,"sourceCodeEnd":179,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/server/src/routes/health.ts#L143-L179","documentation":"This is an HTTP 503 JSON error from POST /dev-server/restart in the health router. The route first confirms a restart is actually required from persisted dev-server supervisor status; only then does it need the database handle to run a preflight query for active heartbeat runs before writing a hot-restart intent. When the `db` handle was never injected into healthRoutes (it is optional), the route cannot safely coordinate the restart and returns `database_unavailable` with status 503.","triggerScenarios":"POST /api/dev-server/restart is called when the persisted dev-server status says a restart IS required (dirty, changedPathCount > 0, or pendingMigrations) but the server was constructed without a `db` instance, so the preflight active-run check cannot run.","commonSituations":"Starting the server in a mode where the DB client is intentionally not wired (embedded PGlite not initialized, DATABASE_URL unset and init failed); clicking \"Restart now\" in the dev UI while the server booted degraded without a database; partial startup where health routes registered before DB init completed.","solutions":["Restart the server ensuring the database initializes before health routes are wired (set DATABASE_URL or allow embedded PGlite setup to complete).","Check startup logs for DB init failure (connection refused, bad DATABASE_URL) and fix the connection first.","If using embedded dev DB, remove data/pglite and re-run `pnpm dev` for a clean initialization.","Verify the code path that constructs healthRoutes passes the `db` argument once the DB client exists.","As a stopgap, perform a cold restart of the dev server instead of the hot-restart endpoint."],"exampleFix":"// before: health routes wired without a db handle when DB is optional\ncreateHealthRouter({ deploymentMode, authReady });\n// after: pass the initialized db so preflight can run\nif (!db) throw new Error(\"database unavailable at startup\");\ncreateHealthRouter({ deploymentMode, authReady }, db);","handlingStrategy":"retry","validationCode":"// before calling the restart endpoint\nconst health = await fetch('/api/health').then(r => r.json());\nif (!health.database || health.database.status !== 'ok') {\n  throw new Error('database not available; hot restart cannot preflight active runs');\n}","typeGuard":"function isDbUnavailable(err: { status?: number; error?: string }): boolean {\n  return err.status === 503 && err.error === 'database_unavailable';\n}","tryCatchPattern":"const res = await api.post('/api/dev-server/restart');\nif (res.status === 503 && (await res.json()).error === 'database_unavailable') {\n  await waitForDbReady({ timeoutMs: 30000 });\n  return retryRestart();\n}","preventionTips":["Gate the dev-server \"Restart now\" UI on a healthy /api/health database status.","Ensure the db client is fully initialized before registering health routes.","Set and validate DATABASE_URL at startup; fail fast with a clear message.","In embedded-PGlite dev, avoid deleting data/pglite while the server is running."],"tags":["database","http-503","dev-server","restart"],"backgroundTag":"database-unavailable","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}