paperclipai/paperclip · error
native_runner_authority_rotation_requires_settled_state
native_runner_authority_rotation_requires_settled_state
Error message
native_runner_authority_rotation_requires_settled_state
What it means
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.
Source
Thrown at packages/paperclip-runner/src/live/runnerd-codex-transport.ts:206
value.runnerInstanceId === expected.runnerInstanceId &&
value.environmentLeaseId === expected.environmentLeaseId &&
value.runId === expected.runId &&
value.normalizedSessionId === expected.normalizedSessionId &&
value.turnId === expected.turnId &&
value.itemId === expected.itemId
);
}
function assertSuspendedRunnerState(
state: Record<string, unknown>,
expected: DurableRecoveryIdentity,
): void {
if (
state.schema !== "paperclip.runner.durable.state.v1" ||
!recoveryIdentityMatches(state, expected) ||
state.lifecycle !== "suspended"
) {
throw new Error("native_runner_authority_rotation_requires_settled_state");
}
}
function assertRealDirectory(path: string): void {
const metadata = lstatSync(path);
if (metadata.isSymbolicLink() || !metadata.isDirectory()) {
throw new Error("native_runner_authority_archive_unsafe");
}
}
function quarantineLocalRuntimeState(root: string, reason: unknown): never {
assertRealDirectory(root);
const quarantine = resolve(
dirname(root),
`${basename(root)}.quarantine-${randomUUID()}`,
);
renameSync(root, quarantine);
mkdirSync(root, { mode: 0o700 });View on GitHub (pinned to 01ad858492)
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'
Example fix
// before await transport.rotateExternalAuthorityEpoch(expected); // throws if lifecycle !== "suspended" // after await runner.stopTurn(); await runner.suspend(); await runner.waitUntilSuspended(); await transport.rotateExternalAuthorityEpoch(expected);
Defensive patterns
Strategy: try-catch
Validate before calling
function canRotate(state: { schema: string; lifecycle: string }) {
return state.schema === "paperclip.runner.durable.state.v1" && state.lifecycle === "suspended";
} Type guard
function isSettled(s: unknown): s is { schema: "paperclip.runner.durable.state.v1"; lifecycle: "suspended" } {
const r = s as any;
return r?.schema === "paperclip.runner.durable.state.v1" && r?.lifecycle === "suspended";
} Try / catch
try {
await transport.rotateExternalAuthorityEpoch(expected);
} catch (err) {
if (err.code === "native_runner_authority_rotation_requires_settled_state") {
await runner.suspend();
await transport.rotateExternalAuthorityEpoch(expected);
} else throw err;
} Prevention
- 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
When it happens
Trigger: 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').
Common situations: 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.
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
- PRP recovery identity does not match the durable session bin
- native_runner_authority_rotation_state_unavailable
- stop the active turn before reconnecting the Codex transport
- native_runner_control_plane_state_unsafe
- native_runner_authority_rotation_state_unsafe
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/a0c931c9499cfc47.
Report an issue: GitHub.