{"record":{"id":"73c4c14b53ed1944","repo":"paperclipai/paperclip","slug":"could-not-resolve-database-connection-from-configpath","errorCode":null,"errorMessage":"Could not resolve database connection from ${configPath}","messagePattern":"Could not resolve database connection from (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/db/scripts/create-auth-bootstrap-invite.ts","lineNumber":57,"sourceCode":"  // Bind bootstrap to this data directory's running process, never another instance.\n  let embeddedPort: number | undefined;\n  if (config.database?.mode !== \"postgres\") {\n    const dataDir = config.database?.embeddedPostgresDataDir;\n    if (!dataDir) throw new Error(\"Embedded bootstrap requires its configured data directory\");\n    const pidLines = readFileSync(path.join(dataDir, \"postmaster.pid\"), \"utf8\").split(/\\r?\\n/);\n    if (realpathSync(pidLines[1] ?? \"\") !== realpathSync(dataDir)) throw new Error(\"Embedded bootstrap data directory does not match the running postmaster\");\n    const postmasterPid = Number(pidLines[0]);\n    if (!Number.isInteger(postmasterPid) || postmasterPid <= 1) throw new Error(\"Invalid embedded postmaster PID\");\n    process.kill(postmasterPid, 0);\n    embeddedPort = Number(pidLines[3]);\n    if (!Number.isInteger(embeddedPort) || embeddedPort < 1 || embeddedPort > 65535) throw new Error(\"Invalid running embedded database port\");\n  }\n  const dbUrl =\n    config.database?.mode === \"postgres\"\n      ? config.database.connectionString\n      : `postgres://paperclip:paperclip@127.0.0.1:${embeddedPort}/paperclip`;\n  if (!dbUrl) {\n    throw new Error(`Could not resolve database connection from ${configPath}`);\n  }\n\n  const db = createDb(dbUrl);\n  const closableDb = db as typeof db & {\n    $client?: {\n      end?: (options?: { timeout?: number }) => Promise<void>;\n    };\n  };\n\n  try {\n    const now = new Date();\n    await db\n      .update(invites)\n      .set({ revokedAt: now, updatedAt: now })\n      .where(\n        and(\n          eq(invites.inviteType, \"bootstrap_ceo\"),\n          isNull(invites.revokedAt),","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/packages/db/scripts/create-auth-bootstrap-invite.ts#L39-L75","documentation":"Thrown when the bootstrap script finishes resolving its database URL and the result is still falsy. This happens only in postgres mode when config.database.connectionString is missing or empty, because in embedded mode the URL is synthesized from the postmaster port and is always truthy. The template interpolates the --config path so you can see which config file failed to yield a connection.","triggerScenarios":"Running `tsx create-auth-bootstrap-invite.ts --config <path> --base-url <url>` with a config JSON where database.mode === \"postgres\" but database.connectionString is absent, null, or the empty string.","commonSituations":"Config file written for embedded mode then hand-switched to \"postgres\" without adding a connectionString; typo like connectionUrl/connection_uri instead of connectionString; config generated by a tool that omits the field when mode is postgres; environment-specific config missing the secret connection string in CI.","solutions":["Add a non-empty database.connectionString to the config file passed via --config","Check the field name is exactly connectionString (not connectionUrl or url)","If you meant to use the embedded database, set database.mode to something other than \"postgres\" (or remove mode) and ensure embeddedPostgresDataDir points at the live data dir","Validate the config JSON parses and contains a database object before running the script"],"exampleFix":"// before (config.json)\n{ \"database\": { \"mode\": \"postgres\" } }\n// after\n{ \"database\": { \"mode\": \"postgres\", \"connectionString\": \"postgres://user:pass@127.0.0.1:5432/paperclip\" } }","handlingStrategy":"validation","validationCode":"const config = JSON.parse(readFileSync(configPath, \"utf8\"));\nconst needsConnectionString = config.database?.mode === \"postgres\";\nif (needsConnectionString && !config.database.connectionString) {\n  throw new Error(`${configPath}: database.connectionString is required when mode is \"postgres\"`);\n}","typeGuard":"function hasConnectionString(c: unknown): c is { database: { mode: \"postgres\"; connectionString: string } } {\n  const cfg = c as { database?: { mode?: string; connectionString?: string } };\n  return cfg?.database?.mode === \"postgres\" && typeof cfg.database.connectionString === \"string\" && cfg.database.connectionString.length > 0;\n}","tryCatchPattern":"try {\n  await runBootstrapInvite(configPath, baseUrl);\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith(\"Could not resolve database connection\")) {\n    console.error(`Fix ${configPath}: add database.connectionString for mode \"postgres\"`);\n    process.exit(1);\n  } else throw err;\n}","preventionTips":["Keep a schema/validator (e.g. zod) over the config JSON before consuming it","Spell the field exactly as connectionString; do not invent variants like connectionUri","When switching a config from embedded to postgres mode, always add the connection string in the same edit","Keep environment-specific connection strings out of hand-rolled JSON; generate configs from a template"],"tags":["database","config","connection-string","missing-field"],"backgroundTag":"missing-required-config-field","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}