paperclipai/paperclip · critical
Persisted Cloud runtime identity does not match PAPERCLIP_CL
Error message
Persisted Cloud runtime identity does not match PAPERCLIP_CLOUD_STACK_ID
What it means
Paperclip's Cloud runtime identity is durably persisted in the instance_settings table on first claim. On every startup, initializeCloudRuntimeIdentity reads that persisted row and calls assertPersistedIdentityMatchesStack, which requires PAPERCLIP_CLOUD_STACK_ID in the environment to be present and exactly equal to the stackId stored in the durable claim. This error means the process is running with a stack id that does not match the one this database instance was claimed by, so the runtime refuses to start with a mismatched Cloud identity. It is a fail-fast guard against pointing a claimed instance at the wrong Cloud stack.
Source
Thrown at server/src/services/cloud-runtime-identity.ts:160
const existingCandidates = (() => {
try {
const parsed = JSON.parse(env.PAPERCLIP_RUNTIME_API_CANDIDATES_JSON ?? "[]");
return Array.isArray(parsed) ? parsed.filter((value): value is string => typeof value === "string") : [];
} catch {
return [];
}
})();
env.PAPERCLIP_RUNTIME_API_CANDIDATES_JSON = JSON.stringify([
identity.canonicalOrigin,
...existingCandidates.filter((candidate) => candidate !== identity.canonicalOrigin),
]);
}
function assertPersistedIdentityMatchesStack(row: PersistedRuntimeIdentity, env: NodeJS.ProcessEnv) {
const configuredStackId = nonEmpty(env.PAPERCLIP_CLOUD_STACK_ID);
if (!configuredStackId || configuredStackId !== row.stackId) {
throw new Error("Persisted Cloud runtime identity does not match PAPERCLIP_CLOUD_STACK_ID");
}
if (!exactHttpsOrigin(row.previousOrigin) || !exactHttpsOrigin(row.canonicalOrigin)) {
throw new Error("Persisted Cloud runtime identity contains an invalid origin");
}
if (!STACK_SLUG_PATTERN.test(row.stackSlug) || new URL(row.canonicalOrigin).hostname.split(".")[0] !== row.stackSlug) {
throw new Error("Persisted Cloud runtime identity contains an invalid stack slug");
}
}
/** Load the durable claim before auth, routes, and child-runtime configuration. */
export async function initializeCloudRuntimeIdentity(
db: Db,
env: NodeJS.ProcessEnv = process.env,
): Promise<CloudRuntimeIdentitySnapshot | null> {
startupOrigin = configuredStartupOrigin(env);
// Self-hosted servers have no Cloud stack identity to restore. Avoid touching
// the singleton table on that path; besides keeping the feature inert, this
// preserves lightweight startup/test database seams that intentionally doView on GitHub (pinned to 01ad858492)
Solutions
- Set PAPERCLIP_CLOUD_STACK_ID to the exact stackId recorded in the persisted claim (inspect the instanceSettings row with singletonKey 'cloud-runtime-identity/v1' and compare its general.stackId).
- If the instance genuinely belongs to a new stack, provision a fresh database for it rather than reusing the claimed database, because the durable claim is single-assignment.
- If the env var is simply missing at this startup, restore it from the deployment secret/config that originally supplied it.
- If the database row is stale (e.g. an abandoned stack's claim), delete the 'cloud-runtime-identity/v1' instance_settings row only if you are certain the instance was never claimed by an active stack, then restart.
Example fix
// before (deployment env) PAPERCLIP_CLOUD_STACK_ID=stack-old-id // after (matches the persisted claim's stackId) PAPERCLIP_CLOUD_STACK_ID=stack-current-id
Defensive patterns
Strategy: validation
Validate before calling
// before startup / deploy
const configured = process.env.PAPERCLIP_CLOUD_STACK_ID?.trim();
if (!configured) throw new Error("PAPERCLIP_CLOUD_STACK_ID must be set in Cloud mode");
// compare against the durable claim
const row = await db.select().from(instanceSettings)
.where(eq(instanceSettings.singletonKey, "cloud-runtime-identity/v1")).limit(1);
if (row[0] && row[0].general.stackId !== configured) {
throw new Error(`Stack id mismatch: env=${configured} persisted=${row[0].general.stackId}`);
} Type guard
function hasMatchingStackId(env: NodeJS.ProcessEnv, row: { stackId: string } | null): boolean {
const configured = env.PAPERCLIP_CLOUD_STACK_ID?.trim();
return typeof configured === "string" && configured.length > 0 && row !== null && configured === row.stackId;
} Prevention
- Keep PAPERCLIP_CLOUD_STACK_ID in the same deployment secret/config set as the database so they move together
- Never restore a claimed database into a different Cloud stack without re-provisioning
- Add a deploy-time check comparing the env var with the persisted claim before restarting the server
- Pin the stack id in infrastructure-as-code so it cannot drift between environments
When it happens
Trigger: initializeCloudRuntimeIdentity is called at startup with PAPERCLIP_CLOUD_STACK_ID set (Cloud mode), a persisted identity row exists, and either (a) PAPERCLIP_CLOUD_STACK_ID is unset/empty/whitespace at this startup even though a claim exists, or (b) its trimmed value differs from row.stackId stored in the durable claim.
Common situations: Operators redeploy the instance pointing at the same database but with a wrong or missing PAPERCLIP_CLOUD_STACK_ID env var; an environment/config change (e.g. a stack was recreated with a new id) reuses the old database; a copy of a production database is restored into a different Cloud stack; the env var is injected with different casing or a stale value by the deployment tooling.
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
- Persisted Cloud runtime identity contains an invalid origin
- Persisted Cloud runtime identity contains an invalid stack s
- ${label} has existing tables but no migration journal. Run m
- failed company transfer runs stranded in applying by a resta
- Warm run transition target conflicts with its durable receip
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/6896c0bd4af0ef27.
Report an issue: GitHub.