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

  1. Ensure the runner is fully suspended (stop the active turn, let it settle) before rotating authority
  2. Check the state file's schema and lifecycle fields; upgrade/downgrade the runner so schemas match
  3. Re-bind the session so the expected identity matches the durable state, or delete stale state so it re-initializes
  4. 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

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


AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10). Data as JSON: /api/errors/a0c931c9499cfc47. Report an issue: GitHub.