paperclipai/paperclip · error
native_runner_authority_rotation_state_unsafe
native_runner_authority_rotation_state_unsafe
Error message
native_runner_authority_rotation_state_unsafe
What it means
readRunnerState() applies the same safety validation as the control-plane reader but to runner-state.json, with a tighter 16 MiB cap. It throws native_runner_authority_rotation_state_unsafe when the file is a symlink, not a regular file, or oversized, because the runner state file backs authority epoch rotation and must not come from an untrusted or anomalous path.
Source
Thrown at packages/paperclip-runner/src/live/runnerd-codex-transport.ts:170
const metadata = lstatSync(path);
if (
metadata.isSymbolicLink() ||
!metadata.isFile() ||
metadata.size > 64 * 1024 * 1024
) {
throw new Error("native_runner_control_plane_state_unsafe");
}
return record(JSON.parse(readFileSync(path, "utf8")));
}
function readRunnerState(path: string): Record<string, unknown> {
const metadata = lstatSync(path);
if (
metadata.isSymbolicLink() ||
!metadata.isFile() ||
metadata.size > 16 * 1024 * 1024
) {
throw new Error("native_runner_authority_rotation_state_unsafe");
}
return record(JSON.parse(readFileSync(path, "utf8")));
}
function controlPlaneIdentity(
state: Record<string, unknown>,
): DurableRecoveryIdentity {
return structuredClone(
record(state.identity) as unknown as DurableRecoveryIdentity,
);
}
function recoveryIdentityMatches(
value: DurableRecoveryIdentity | Record<string, unknown>,
expected: DurableRecoveryIdentity,
): boolean {
return (
value.runnerInstanceId === expected.runnerInstanceId &&View on GitHub (pinned to 01ad858492)
Solutions
- Check `ls -la runner-state.json`; remove symlinks and restore a plain regular file
- Verify size is under 16 MiB; truncate/regenerate the state file from a known-good copy
- Exclude the runner state directory from symlink-creating sync/backup tools
- If unrecoverable, archive the whole state root and let the runner re-initialize it
Example fix
// before
const st = transport.runnerState();
// after
const st = fs.lstatSync(runnerStatePath);
if (!st.isSymbolicLink() && fs.statSync(runnerStatePath).isFile() && st.size <= 16 * 1024 * 1024) {
const state = transport.runnerState();
} Defensive patterns
Strategy: validation
Validate before calling
import { lstatSync, statSync } from "node:fs";
function isSafeRunnerState(p: string): boolean {
const m = lstatSync(p);
return !m.isSymbolicLink() && statSync(p).isFile() && m.size <= 16 * 1024 * 1024;
} Try / catch
try {
const state = transport.runnerState();
} catch (err) {
if (err.code === "native_runner_authority_rotation_state_unsafe") {
logger.error("runner-state.json unsafe (symlink/special/oversized)");
restoreRunnerStateFromBackup();
} else throw err;
} Prevention
- Keep runner-state.json a plain regular file in a real directory
- Keep the state dir out of dotfile managers and sync tools
- Alert on abnormal growth of runner-state.json (16 MiB cap)
- Validate state after crashes and restore from archive backups rather than hand-editing
When it happens
Trigger: Calling runnerState() or candidateRunnerState() (or triggering rotateExternalAuthorityEpoch/#closeOnce flows) while runner-state.json is a symlink, special file, or larger than 16 MiB.
Common situations: User symlinked runner-state.json to a shared config; a previous crash left a corrupted multi-megabyte state file; tests running against tmpdirs with odd file types; antivirus or sync tools (Dropbox) replacing the file with links.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- native_runner_control_plane_state_unsafe
- native_runner_authority_archive_unsafe
- native_runner_authority_rotation_state_unavailable
- codex auth cache: account_id is not a valid account handle
- codex auth cache: account-home directory no longer exists; r
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/03997d25cabb2613.
Report an issue: GitHub.