paperclipai/paperclip · error

native_runner_control_plane_state_unsafe

native_runner_control_plane_state_unsafe

Error message

native_runner_control_plane_state_unsafe

What it means

readControlPlaneState() validates the on-disk control-plane state file with lstatSync before reading it. It refuses to load anything that is a symlink, not a regular file, or larger than 64 MiB, throwing native_runner_control_plane_state_unsafe. This is a tamper/safety guard: the control-plane state drives authority and resume decisions, so an unexpected file shape must not be trusted or parsed.

Source

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

export function withCodexCollaborationRuntimeInstructions(
  instructions: string,
  enabled = true,
): string {
  if (!enabled) return instructions;
  const base = instructions.trimEnd();
  return `${base}\n\n${CODEX_COLLABORATION_RUNTIME_INSTRUCTIONS}`;
}

function readControlPlaneState(directory: string): Record<string, unknown> {
  const path = resolve(directory, "control-plane-state.json");
  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>,

View on GitHub (pinned to 01ad858492)

Solutions

  1. Inspect the file: `ls -la` the path; replace any symlink with a real regular file
  2. Check file size (`stat -c %s`); if over 64 MiB, restore state from a valid backup or re-initialize the runner state directory
  3. If the state directory is corrupted, let the quarantine/re-init path recreate it rather than hand-editing the file
  4. Verify the runner is pointed at the correct state root (config mistake can make it read a foreign file)

Example fix

// before
const state = transport.state; // throws native_runner_control_plane_state_unsafe
// after
import { statSync } from "node:fs";
const meta = statSync(controlPlaneStatePath);
if (meta.isFile() && meta.size <= 64 * 1024 * 1024 && !lstatSync(controlPlaneStatePath).isSymbolicLink()) {
  const state = transport.state;
}
Defensive patterns

Strategy: validation

Validate before calling

import { lstatSync, statSync } from "node:fs";
function isSafeStateFile(p: string): boolean {
  const m = lstatSync(p);
  return !m.isSymbolicLink() && statSync(p).isFile() && m.size <= 64 * 1024 * 1024;
}

Try / catch

try {
  const state = transport.state;
} catch (err) {
  if (err.code === "native_runner_control_plane_state_unsafe") {
    logger.error("control-plane state file unsafe; restore from backup", { path });
    await recoverFromQuarantineOrReinit();
  } else throw err;
}

Prevention

When it happens

Trigger: The control-plane state path is a symlink, a socket/directory/device instead of a regular file, or has grown beyond the 64 MiB cap when state(), archivedIdentity(), or #resume reads it.

Common situations: Operator replaced the state file with a symlink into a mount; a runaway writer corrupted/blew up the state file; running the runner against a bind-mounted or container-virtualized path where lstat attributes differ.

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


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