paperclipai/paperclip · error
Could not resolve database connection from
Error message
Could not resolve database connection from ${configPath} What it means
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.
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
Example fix
// before (config.json)
{ "database": { "mode": "postgres" } }
// after
{ "database": { "mode": "postgres", "connectionString": "postgres://user:pass@127.0.0.1:5432/paperclip" } } Defensive patterns
Strategy: validation
Validate before calling
const config = JSON.parse(readFileSync(configPath, "utf8"));
const needsConnectionString = config.database?.mode === "postgres";
if (needsConnectionString && !config.database.connectionString) {
throw new Error(`${configPath}: database.connectionString is required when mode is "postgres"`);
} Type guard
function hasConnectionString(c: unknown): c is { database: { mode: "postgres"; connectionString: string } } {
const cfg = c as { database?: { mode?: string; connectionString?: string } };
return cfg?.database?.mode === "postgres" && typeof cfg.database.connectionString === "string" && cfg.database.connectionString.length > 0;
} Try / catch
try {
await runBootstrapInvite(configPath, baseUrl);
} catch (err) {
if (err instanceof Error && err.message.startsWith("Could not resolve database connection")) {
console.error(`Fix ${configPath}: add database.connectionString for mode "postgres"`);
process.exit(1);
} else throw err;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.
Related errors
- CreateOS requires a shape from its shape catalog.
- database-contract-unmet
- Embedded bootstrap data directory does not match the…
- Embedded bootstrap requires its configured data directory
- Invalid embedded postmaster PID
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/73c4c14b53ed1944.
Report an issue: GitHub.
Appendix: source
Thrown at packages/db/scripts/create-auth-bootstrap-invite.ts:57
// Bind bootstrap to this data directory's running process, never another instance.
let embeddedPort: number | undefined;
if (config.database?.mode !== "postgres") {
const dataDir = config.database?.embeddedPostgresDataDir;
if (!dataDir) throw new Error("Embedded bootstrap requires its configured data directory");
const pidLines = readFileSync(path.join(dataDir, "postmaster.pid"), "utf8").split(/\r?\n/);
if (realpathSync(pidLines[1] ?? "") !== realpathSync(dataDir)) throw new Error("Embedded bootstrap data directory does not match the running postmaster");
const postmasterPid = Number(pidLines[0]);
if (!Number.isInteger(postmasterPid) || postmasterPid <= 1) throw new Error("Invalid embedded postmaster PID");
process.kill(postmasterPid, 0);
embeddedPort = Number(pidLines[3]);
if (!Number.isInteger(embeddedPort) || embeddedPort < 1 || embeddedPort > 65535) throw new Error("Invalid running embedded database port");
}
const dbUrl =
config.database?.mode === "postgres"
? config.database.connectionString
: `postgres://paperclip:paperclip@127.0.0.1:${embeddedPort}/paperclip`;
if (!dbUrl) {
throw new Error(`Could not resolve database connection from ${configPath}`);
}
const db = createDb(dbUrl);
const closableDb = db as typeof db & {
$client?: {
end?: (options?: { timeout?: number }) => Promise<void>;
};
};
try {
const now = new Date();
await db
.update(invites)
.set({ revokedAt: now, updatedAt: now })
.where(
and(
eq(invites.inviteType, "bootstrap_ceo"),
isNull(invites.revokedAt),View on GitHub (pinned to 3f1d897a7c)