paperclipai/paperclip · critical

Refusing to reuse PostgreSQL: its data directory belongs to…

Error message

Refusing to reuse PostgreSQL: its data directory belongs to another instance.

What it means

During startup, startServerWithDatabaseTeardown detects an already-running embedded PostgreSQL via its pid file and verifies — by querying `SHOW data_directory` through the running server — that the live process's data directory matches the configured one. If the resolved directories differ, it throws rather than reusing a PostgreSQL instance whose storage belongs to another Paperclip instance, preventing silent cross-instance data corruption.

Solutions

  1. Shut down the foreign postgres process (find via `lsof -i :<port>` / `ps`), remove the stale pid file, and restart so this instance boots its own PostgreSQL
  2. Give this instance a unique data directory if you intentionally run multiple instances
  3. Reset the dev database (`rm -rf data/pglite && pnpm dev`) if the directory was copied from elsewhere
  4. Verify PGDATA of the running server (`SELECT setting FROM pg_settings WHERE name='data_directory'`) matches your intended data dir

Example fix

# before: reusing another instance's postgres
pnpm dev
// after: clear the foreign process/state, then start fresh
kill <foreign-postgres-pid>; rm -rf data/pglite; pnpm dev
Defensive patterns

Strategy: try-catch

Validate before calling

const actual = await getPostgresDataDirectory(adminConn);
if (typeof actual !== "string" || resolve(actual) !== resolve(expectedDataDir)) {
  throw new Error("running postgres data directory does not match; refusing to start");
}

Type guard

function dataDirMatches(actual: unknown, expected: string): actual is string {
  return typeof actual === "string" && resolve(actual) === resolve(expected);
}

Try / catch

try { await startServerWithDatabaseTeardown(...); }
catch (e) {
  if (String(e.message).includes("data directory belongs to another instance")) {
    console.error("Stop the foreign postgres process and remove the stale pid file before restarting.");
  }
  throw e;
}

Prevention

When it happens

Trigger: A postmaster pid file exists (runningPid truthy), embeddedPostgresOwnerPort accepted it, but getPostgresDataDirectory over postgres://paperclip:paperclip@127.0.0.1:<port>/postgres returns a non-string or a directory that does not resolve to the configured dataDir.

Common situations: Port collision avoidance made a previous instance's PostgreSQL take the port, and a new instance's stale pid file points at that foreign server; two clones of the repo sharing a data directory; a container restart where a leftover postgres from another mount is still listening.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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

Appendix: source

Thrown at server/src/index.ts:519

    const getRunningPid = (): number | null => {
      if (!existsSync(postmasterPidFile)) return null;
      try {
        const pidLine = readFileSync(postmasterPidFile, "utf8").split("\n")[0]?.trim();
        const pid = Number(pidLine);
        if (!Number.isInteger(pid) || pid <= 0) return null;
        if (!isPidRunning(pid)) return null;
        return pid;
      } catch {
        return null;
      }
    };
  
    const runningPid = getRunningPid();
    if (runningPid) {
      port = embeddedPostgresOwnerPort(readFileSync(postmasterPidFile, "utf8"), dataDir, runningPid);
      const actualDataDir = await getPostgresDataDirectory(`postgres://paperclip:paperclip@127.0.0.1:${port}/postgres`);
      if (typeof actualDataDir !== "string" || resolve(actualDataDir) !== resolve(dataDir)) {
        throw new Error("Refusing to reuse PostgreSQL: its data directory belongs to another instance.");
      }
      logger.warn(`Embedded PostgreSQL already running; reusing existing process (pid=${runningPid}, port=${port})`);
    } else {
      const configuredAdminConnectionString = `postgres://paperclip:paperclip@127.0.0.1:${configuredPort}/postgres`;
      try {
        const actualDataDir = await getPostgresDataDirectory(configuredAdminConnectionString);
        if (
          typeof actualDataDir !== "string" ||
          resolve(actualDataDir) !== resolve(dataDir)
        ) {
          throw new Error("reachable postgres does not use the expected embedded data directory");
        }
        await ensurePostgresDatabase(configuredAdminConnectionString, "paperclip");
        logger.warn(
          `Embedded PostgreSQL appears to already be reachable without a pid file; reusing existing server on configured port ${configuredPort}`,
        );
      } catch {
        const detectedPort = await detectPort(configuredPort);

View on GitHub (pinned to 3f1d897a7c)