{"record":{"id":"e63df1d699a3c3b0","repo":"paperclipai/paperclip","slug":"prp-recovery-identity-does-not-match-the-durable-s","errorCode":null,"errorMessage":"PRP recovery identity does not match the durable session binding","messagePattern":"PRP recovery identity does not match the durable session binding","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/paperclip-runner/src/live/runnerd-codex-transport.ts","lineNumber":288,"sourceCode":"      return state;\n    }\n  }\n  return null;\n}\n\nfunction rotateLocalAuthorityEpoch(\n  root: string,\n  controlPlaneState: Record<string, unknown>,\n  desired: DurableRecoveryIdentity,\n): Record<string, unknown> {\n  const priorIdentity = controlPlaneIdentity(controlPlaneState);\n  if (\n    priorIdentity.runnerInstanceId !== desired.runnerInstanceId ||\n    priorIdentity.environmentLeaseId !== desired.environmentLeaseId ||\n    priorIdentity.normalizedSessionId !== desired.normalizedSessionId ||\n    priorIdentity.runId === desired.runId\n  ) {\n    throw new Error(\n      \"PRP recovery identity does not match the durable session binding\",\n    );\n  }\n  const runnerDirectory = resolve(root, \"runner\");\n  const runnerStatePath = resolve(runnerDirectory, \"runner-state.json\");\n  const archive = authorityArchiveDirectory(root, priorIdentity);\n  const archivedControlPlane = resolve(archive, \"control-plane\");\n  const archivedRunnerState = resolve(archive, \"runner-state.json\");\n  const runnerStateSource = existsSync(runnerStatePath)\n    ? runnerStatePath\n    : archivedRunnerState;\n  if (!existsSync(runnerStateSource)) {\n    throw new Error(\"native_runner_authority_rotation_state_unavailable\");\n  }\n  assertRealDirectory(runnerDirectory);\n  const runnerState = readRunnerState(runnerStateSource);\n  if (\n    runnerState.runnerInstanceId !== priorIdentity.runnerInstanceId ||","sourceCodeStart":270,"sourceCodeEnd":306,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/packages/paperclip-runner/src/live/runnerd-codex-transport.ts#L270-L306","documentation":"During local authority epoch rotation, rotateLocalAuthorityEpoch() compares the identity read from the prior durable state against the desired recovery identity. The runnerInstanceId, environmentLeaseId, and normalizedSessionId must match exactly, and the runId must differ (proving this is a new epoch of the same session, not a foreign or identical state). On any mismatch it throws 'PRP recovery identity does not match the durable session binding'.","triggerScenarios":"#resume() requests rotation when the on-disk prior state belongs to a different runner instance, a different environment lease, a different session, or when the runId is unchanged (no epoch progress).","commonSituations":"Pointing a new session at a reused state directory from another session/lease; recycled environment lease IDs after infra churn; stale state not archived between runs; attempt to rotate twice with the same runId.","solutions":["Verify the session/lease you are resuming with actually owns this state root; use the original session's identity or start fresh state","If the state belongs to another session, move or delete it so rotation does not run against foreign identity","Ensure each rotation attempt uses a new runId; the prior and desired runId must differ","Check for concurrent rotators — another process may have already rotated, changing the prior identity"],"exampleFix":"// before\nawait transport.resume({ runnerInstanceId: \"other-instance\", environmentLeaseId: leaseB, ... });\n// after\nawait transport.resume({\n  runnerInstanceId: priorState.runnerInstanceId,\n  environmentLeaseId: priorState.environmentLeaseId,\n  normalizedSessionId: priorState.normalizedSessionId,\n  runId: nextRunId, // must differ from priorState.runId\n});","handlingStrategy":"validation","validationCode":"function identityMatchesBinding(prior: Identity, desired: Identity): boolean {\n  return prior.runnerInstanceId === desired.runnerInstanceId &&\n    prior.environmentLeaseId === desired.environmentLeaseId &&\n    prior.normalizedSessionId === desired.normalizedSessionId &&\n    prior.runId !== desired.runId;\n}","typeGuard":null,"tryCatchPattern":"try {\n  await transport.resume(desired);\n} catch (err) {\n  if (err.message.includes(\"PRP recovery identity does not match\")) {\n    logger.error(\"resume identity != durable binding; use owning session or fresh state\");\n  } else throw err;\n}","preventionTips":["Track which session/lease owns each state root; never resume with another session's identity","Ensure each rotation/resume uses a fresh runId","Avoid cloning or copying state directories between environments","Guard against concurrent resumers of the same state root"],"tags":["identity","authority-rotation","codex","state-file"],"backgroundTag":"invalid-argument-value","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}