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
- 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
- Give this instance a unique data directory if you intentionally run multiple instances
- Reset the dev database (`rm -rf data/pglite && pnpm dev`) if the directory was copied from elsewhere
- 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
- Give each instance/clone its own data directory
- Kill leftover postgres processes before restarting an instance
- Check `lsof -i :<port>` for foreign listeners before startup
- Avoid copying data/ directories between workspaces while servers run
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
- The running embedded PostgreSQL identity does not match…
- Database mode is postgres but no connection string was set…
- Cannot seed target embedded PostgreSQL at
- CLI auth challenge was cancelled.
- database-contract-unmet
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)