{"record":{"id":"a0c931c9499cfc47","repo":"paperclipai/paperclip","slug":"native-runner-authority-rotation-requires-settled","errorCode":"native_runner_authority_rotation_requires_settled_state","errorMessage":"native_runner_authority_rotation_requires_settled_state","messagePattern":"native_runner_authority_rotation_requires_settled_state","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/paperclip-runner/src/live/runnerd-codex-transport.ts","lineNumber":206,"sourceCode":"    value.runnerInstanceId === expected.runnerInstanceId &&\n    value.environmentLeaseId === expected.environmentLeaseId &&\n    value.runId === expected.runId &&\n    value.normalizedSessionId === expected.normalizedSessionId &&\n    value.turnId === expected.turnId &&\n    value.itemId === expected.itemId\n  );\n}\n\nfunction assertSuspendedRunnerState(\n  state: Record<string, unknown>,\n  expected: DurableRecoveryIdentity,\n): void {\n  if (\n    state.schema !== \"paperclip.runner.durable.state.v1\" ||\n    !recoveryIdentityMatches(state, expected) ||\n    state.lifecycle !== \"suspended\"\n  ) {\n    throw new Error(\"native_runner_authority_rotation_requires_settled_state\");\n  }\n}\n\nfunction assertRealDirectory(path: string): void {\n  const metadata = lstatSync(path);\n  if (metadata.isSymbolicLink() || !metadata.isDirectory()) {\n    throw new Error(\"native_runner_authority_archive_unsafe\");\n  }\n}\n\nfunction quarantineLocalRuntimeState(root: string, reason: unknown): never {\n  assertRealDirectory(root);\n  const quarantine = resolve(\n    dirname(root),\n    `${basename(root)}.quarantine-${randomUUID()}`,\n  );\n  renameSync(root, quarantine);\n  mkdirSync(root, { mode: 0o700 });","sourceCodeStart":188,"sourceCodeEnd":224,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/packages/paperclip-runner/src/live/runnerd-codex-transport.ts#L188-L224","documentation":"assertSuspendedRunnerState() enforces the precondition for authority epoch rotation: the runner durable state must be schema paperclip.runner.durable.state.v1, its recovery identity must match the expected identity, and its lifecycle must be 'suspended'. If any of these fails, rotation would rotate authority for a runner that is not safely parked, so it throws native_runner_authority_rotation_requires_settled_state.","triggerScenarios":"rotateExternalAuthorityEpoch() or #closeOnce() runs while the runner state has a different schema version, a mismatched recovery identity (runnerInstanceId/environmentLeaseId/normalizedSessionId/runId), or lifecycle other than 'suspended' (e.g. 'running' or 'crashed').","commonSituations":"Operator rotates the external authority while a turn is still executing; state file written by an older runner version with a different schema string; leftover state from a different session/lease after a manual state move.","solutions":["Ensure the runner is fully suspended (stop the active turn, let it settle) before rotating authority","Check the state file's schema and lifecycle fields; upgrade/downgrade the runner so schemas match","Re-bind the session so the expected identity matches the durable state, or delete stale state so it re-initializes","Retry the rotation only after the runner reports lifecycle 'suspended'"],"exampleFix":"// before\nawait transport.rotateExternalAuthorityEpoch(expected); // throws if lifecycle !== \"suspended\"\n// after\nawait runner.stopTurn();\nawait runner.suspend();\nawait runner.waitUntilSuspended();\nawait transport.rotateExternalAuthorityEpoch(expected);","handlingStrategy":"try-catch","validationCode":"function canRotate(state: { schema: string; lifecycle: string }) {\n  return state.schema === \"paperclip.runner.durable.state.v1\" && state.lifecycle === \"suspended\";\n}","typeGuard":"function isSettled(s: unknown): s is { schema: \"paperclip.runner.durable.state.v1\"; lifecycle: \"suspended\" } {\n  const r = s as any;\n  return r?.schema === \"paperclip.runner.durable.state.v1\" && r?.lifecycle === \"suspended\";\n}","tryCatchPattern":"try {\n  await transport.rotateExternalAuthorityEpoch(expected);\n} catch (err) {\n  if (err.code === \"native_runner_authority_rotation_requires_settled_state\") {\n    await runner.suspend();\n    await transport.rotateExternalAuthorityEpoch(expected);\n  } else throw err;\n}","preventionTips":["Always suspend and drain turns before any authority rotation","Keep runner and control-plane versions aligned so the schema string matches","Never reuse a state root across sessions/leases","Poll lifecycle state until 'suspended' instead of rotating immediately after stop"],"tags":["lifecycle","authority-rotation","codex","state-file"],"backgroundTag":"invalid-state-transition","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}