paperclipai/paperclip · error

Invalid embedded postmaster PID

Error message

Invalid embedded postmaster PID

What it means

This error is thrown by the create-auth-bootstrap-invite bootstrap script when it parses the embedded PostgreSQL postmaster.pid file and the first line (the PID) is not a usable integer greater than 1. The script must signal the running postmaster (process.kill(pid, 0)) to prove the embedded database is alive before binding the invite creation to it; a malformed PID means the pid file is corrupt, empty, or from a different format, so it refuses to continue rather than killing or connecting to the wrong process.

Solutions

  1. Ensure the embedded Postgres for this data directory is actually running (start the dev server with `pnpm dev`) before running the bootstrap script
  2. Stop everything, delete the stale postmaster.pid (or reset the data dir with `rm -rf data/pglite && pnpm dev` if acceptable), and let a fresh postmaster rewrite it
  3. Verify the config's database.embeddedPostgresDataDir points at the real live data directory, not a copy, backup, or empty folder
  4. If you need no embedded database, set database.mode to "postgres" with a connectionString so the pid file is not consulted at all

Example fix

// before (config.json)
{ "database": { "mode": "embedded", "embeddedPostgresDataDir": "./backup/pglite-copy" } }
// after
{ "database": { "mode": "embedded", "embeddedPostgresDataDir": "./data/pglite" } }
// and start the embedded database first:
// pnpm dev
Defensive patterns

Strategy: validation

Validate before calling

import { readFileSync } from "node:fs";
const pidLines = readFileSync(`${dataDir}/postmaster.pid`, "utf8").split(/\r?\n/);
const pid = Number(pidLines[0]);
if (!Number.isInteger(pid) || pid <= 1) throw new Error("postmaster.pid has no valid PID; start the embedded database first");
process.kill(pid, 0); // throws ESRCH if the process is gone

Type guard

function isValidPid(v: unknown): v is number {
  return typeof v === "number" && Number.isInteger(v) && v > 1;
}

Try / catch

try {
  await runBootstrapInvite(configPath, baseUrl);
} catch (err) {
  if (err instanceof Error && err.message === "Invalid embedded postmaster PID") {
    // regenerate the pid file, then retry once
    await startEmbeddedPostgres(dataDir);
    await runBootstrapInvite(configPath, baseUrl);
  } else throw err;
}

Prevention

When it happens

Trigger: Running `tsx create-auth-bootstrap-invite.ts --config <path> --base-url <url>` against a config whose database.mode is not "postgres" (embedded mode) where the file <embeddedPostgresDataDir>/postmaster.pid exists but its first line, when passed to Number(), is NaN, non-integer, zero, or negative (or 1).

Common situations: Embedded Postgres was never started so an empty or leftover postmaster.pid exists; the data directory was copied or restored and the pid file is truncated; a stale/corrupt pid file left after an unclean shutdown; the config points at a directory that contains a partial or hand-edited postmaster.pid; the database mode was switched between embedded and postgres without cleaning the data dir.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18). Data as JSON: /api/errors/964d3545081db738. Report an issue: GitHub.

Appendix: source

Thrown at packages/db/scripts/create-auth-bootstrap-invite.ts:47

  const config = JSON.parse(readFileSync(path.resolve(configPath), "utf8")) as {
    database?: {
      mode?: string;
      embeddedPostgresPort?: number;
      embeddedPostgresDataDir?: string;
      connectionString?: string;
    };
  };
  // The server can select another port when the configured one is occupied.
  // 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>;
    };
  };

View on GitHub (pinned to 3f1d897a7c)