paperclipai/paperclip · error

native_runner_authority_rotation_state_unavailable

native_runner_authority_rotation_state_unavailable

Error message

native_runner_authority_rotation_state_unavailable

What it means

rotateLocalAuthorityEpoch() needs a readable runner state to validate before rotating: it uses the live runner-state.json if present, else the archived copy inside the authority epoch archive. If neither file exists it throws native_runner_authority_rotation_state_unavailable — the rotation cannot proceed because there is no prior runner state to validate and archive.

Solutions

  1. Confirm you are pointing at the correct state root (a misconfigured root looks identical to a missing state)
  2. Check for leftover quarantine or partially rotated directories (<root>.quarantine-*, authority-epochs) and restore runner-state.json from them
  3. If state is genuinely absent, re-initialize the runner/session instead of rotating (rotation requires prior state)
  4. Restore from backup of the runner state directory before resuming

Example fix

// before
pnpm runner resume --state-root data/runner-state  # runner-state.json deleted by cleanup
// after
ls data/runner-state*/  && find data -name 'runner-state.json'
# restore from archive/quarantine copy:
cp data/runner-state.quarantine-<uuid>/runner/runner-state.json data/runner-state/runner/runner-state.json
Defensive patterns

Strategy: fallback

Validate before calling

import { existsSync } from "node:fs";
function hasRunnerState(root: string): boolean {
  return existsSync(`${root}/runner/runner-state.json`) ||
    existsSync(`${root}/authority-epochs`); // archives may hold prior state
}

Try / catch

try {
  await transport.resume();
} catch (err) {
  if (err.code === "native_runner_authority_rotation_state_unavailable") {
    logger.error("no runner-state.json live or archived; re-init required");
    reinitializeRunnerState();
  } else throw err;
}

Prevention

When it happens

Trigger: #resume() triggers local rotation while both runner-state.json and the archived runner-state.json (under the authority-epochs archive's control-plane/runners layout) are missing.

Common situations: State directory partially cleaned up by an operator or cleanup job; interrupted prior rotation that moved the live file but never archived it; fresh/re-initialized state root that never had runner state; wrong state root configured.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


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

Appendix: source

Thrown at packages/paperclip-runner/src/live/runnerd-codex-transport.ts:302

    priorIdentity.runnerInstanceId !== desired.runnerInstanceId ||
    priorIdentity.environmentLeaseId !== desired.environmentLeaseId ||
    priorIdentity.normalizedSessionId !== desired.normalizedSessionId ||
    priorIdentity.runId === desired.runId
  ) {
    throw new Error(
      "PRP recovery identity does not match the durable session binding",
    );
  }
  const runnerDirectory = resolve(root, "runner");
  const runnerStatePath = resolve(runnerDirectory, "runner-state.json");
  const archive = authorityArchiveDirectory(root, priorIdentity);
  const archivedControlPlane = resolve(archive, "control-plane");
  const archivedRunnerState = resolve(archive, "runner-state.json");
  const runnerStateSource = existsSync(runnerStatePath)
    ? runnerStatePath
    : archivedRunnerState;
  if (!existsSync(runnerStateSource)) {
    throw new Error("native_runner_authority_rotation_state_unavailable");
  }
  assertRealDirectory(runnerDirectory);
  const runnerState = readRunnerState(runnerStateSource);
  if (
    runnerState.runnerInstanceId !== priorIdentity.runnerInstanceId ||
    runnerState.environmentLeaseId !== priorIdentity.environmentLeaseId ||
    runnerState.runId !== priorIdentity.runId ||
    runnerState.normalizedSessionId !== priorIdentity.normalizedSessionId ||
    runnerState.turnId !== priorIdentity.turnId ||
    runnerState.itemId !== priorIdentity.itemId ||
    runnerState.lifecycle !== "suspended"
  ) {
    throw new Error("native_runner_authority_rotation_requires_settled_state");
  }
  const archivesRoot = resolve(root, "authority-epochs");
  if (existsSync(archivesRoot)) {
    assertRealDirectory(archivesRoot);
  } else {

View on GitHub (pinned to 3f1d897a7c)