paperclipai/paperclip · critical

native_runner_state_quarantined

native_runner_state_quarantined

Error message

native_runner_state_quarantined: ${detail}; the prior state was preserved for operator recovery

What it means

quarantineLocalRuntimeState() is the last-resort recovery path: when resuming (#resume) finds state it cannot safely load, it renames the entire state root to '<root>.quarantine-<uuid>', creates a fresh 0700 directory, and throws native_runner_state_quarantined with the original cause embedded. The old state is preserved at the quarantine path for operator recovery — nothing is deleted.

Source

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

}

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 });
  const detail = reason instanceof Error ? reason.message : String(reason);
  throw new Error(
    `native_runner_state_quarantined: ${detail}; the prior state was preserved for operator recovery`,
  );
}

function authorityArchiveDirectory(
  root: string,
  identity: DurableRecoveryIdentity,
): string {
  const digest = createHash("sha256")
    .update(JSON.stringify(identity))
    .digest("hex")
    .slice(0, 24);
  return resolve(root, "authority-epochs", `epoch-${digest}`);
}

function latestArchivedControlPlaneState(
  root: string,
  desired: DurableRecoveryIdentity,

View on GitHub (pinned to 01ad858492)

Solutions

  1. Read the embedded detail after 'native_runner_state_quarantined:' — it names the original failure
  2. Recover data from the sibling directory '<root>.quarantine-<uuid>' left next to the state root
  3. After inspecting/reconciling, delete or move the quarantine directory and restart the runner with fresh state
  4. Fix the root cause (version mismatch, manual edits, corruption) before resuming again, or it will re-quarantine

Example fix

// before
ls data/runner-state            # fresh empty dir
cp -a data/runner-state.quarantine-<uuid>/control-plane data/runner-state/
// after
mv data/runner-state.quarantine-<uuid> data/runner-state.recovered &&
# reconcile/inspect data/runner-state.recovered, then re-init data/runner-state cleanly
Defensive patterns

Strategy: try-catch

Validate before calling

import { existsSync, readdirSync } from "node:fs";
function findQuarantines(root: string): string[] {
  return existsSync(root) ? readdirSync(root, { withFileTypes: false })
    .filter((n) => n.startsWith(".quarantine-")) : [];
}

Try / catch

try {
  await transport.resume();
} catch (err) {
  if (String(err.message).startsWith("native_runner_state_quarantined:")) {
    const cause = String(err.message).split(":")[1];
    logger.error("runner state quarantined; original cause:", { cause });
    inspectQuarantineDirAndReinit();
  } else throw err;
}

Prevention

When it happens

Trigger: #resume() encounters a state-loading/validation error (e.g. unsafe or unparseable state files) that makes continuing unsafe, triggering quarantine of the runtime state root before throwing.

Common situations: Corrupted or hand-edited state after a crash; state written by an incompatible runner version; operator experimentation with the state directory mid-run; disk-level corruption.

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/a1a0270e551404ee. Report an issue: GitHub.