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.

Source

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

    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 01ad858492)

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@01ad858492 (2026-09-10). Data as JSON: /api/errors/a31234a3c5700de3. Report an issue: GitHub.