paperclipai/paperclip · error

native_adopted_runner_state_directory_required

native_adopted_runner_state_directory_required

Error message

native_adopted_runner_state_directory_required

What it means

The CapabilityRunnerdCodexTransport constructor enforces that when adoptExistingRunner is true, a non-empty stateDirectory must be supplied. Adopting an existing native runner requires knowing where its durable state lives; without it the transport cannot attach to prior state.

Source

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

  #failure: Error | null = null;
  readonly #failureSignal: Promise<never>;
  #rejectFailureSignal!: (error: Error) => void;
  #runnerRecoveryInProgress = false;
  #startupComplete = false;
  #startupFailureCode = "native_runner_process_exited";
  #controlPlaneCheckpoint:
    ((settlement: "settled" | "unsettled") => Promise<void> | void) | null =
    null;
  #controlPlaneRelease: (() => Promise<void> | void) | null = null;
  #nextTraceDebugSequence = 1;
  #traceRehydrationSpoolOverflow = false;
  #pendingTraceRehydrations: PendingTraceRehydration[] = [];
  #pendingDriverTraceInterpretations: PendingDriverTraceInterpretation[] = [];
  readonly #bridgedRuntimeInputs = new Map<string, { durableTurnId: string }>();

  constructor(readonly options: CapabilityRunnerdCodexTransportOptions) {
    if (options.adoptExistingRunner && !options.stateDirectory?.trim()) {
      throw new Error("native_adopted_runner_state_directory_required");
    }
    if (options.provider === "acpx" && options.acpxAgent === "pi") {
      throw new Error("The Pi ACPX profile is not available");
    }
    this.#failureSignal = new Promise<never>((_resolve, reject) => {
      this.#rejectFailureSignal = reject;
    });
    // Failure is also observed by request/notification paths. Register an
    // internal handler so a process exit after the owner has closed the
    // session cannot become an unhandled process-level rejection.
    void this.#failureSignal.catch(() => undefined);
    this.#ownsRoot = options.stateDirectory === undefined;
    this.#turnId = options.resumeActiveTurnId ?? "";
    this.#root =
      options.stateDirectory ??
      mkdtempSync(resolve(tmpdir(), "paperclip-runner-lab-prp-"));
    if (options.resumeDynamicTools !== undefined) {
      this.#authorizedTools = authorizedToolSetForProvider(options.provider, [

View on GitHub (pinned to 01ad858492)

Solutions

  1. Pass a non-empty stateDirectory when adoptExistingRunner is true, e.g. stateDirectory: '/var/lib/paperclip/runner'.
  2. Set the env/config value feeding stateDirectory (e.g. PAPERCLIP_RUNNER_STATE_DIR) before constructing the transport.
  3. If not adopting an existing runner, set adoptExistingRunner: false so the check is skipped.
  4. Trim/validate the value at config-load time to fail early with a clearer message.

Example fix

// before
new CapabilityRunnerdCodexTransport({ adoptExistingRunner: true });
// after
new CapabilityRunnerdCodexTransport({ adoptExistingRunner: true, stateDirectory: process.env.RUNNER_STATE_DIR ?? '/var/lib/paperclip/runner' });
Defensive patterns

Strategy: validation

Validate before calling

if (opts.adoptExistingRunner && !(opts.stateDirectory ?? '').trim()) {
  throw new Error('stateDirectory is required when adoptExistingRunner is true');
}

Type guard

const canAdopt = (o: { adoptExistingRunner?: boolean; stateDirectory?: string }): o is Required<typeof o> =>
  !o.adoptExistingRunner || Boolean(o.stateDirectory?.trim());

Try / catch

try {
  transport = new CapabilityRunnerdCodexTransport(options);
} catch (err) {
  if ((err as Error).message === 'native_adopted_runner_state_directory_required') {
    options.stateDirectory = resolveStateDir();
    transport = new CapabilityRunnerdCodexTransport(options);
  } else throw err;
}

Prevention

When it happens

Trigger: new CapabilityRunnerdCodexTransport({ adoptExistingRunner: true, stateDirectory: undefined }) or stateDirectory: '' / ' ' (whitespace-only is also rejected).

Common situations: Config object assembled conditionally where stateDirectory is only set for fresh runners; env var for state dir unset; trimming/empty-string defaults; copying options between adopt and non-adopt code paths.

Understand the failure class

Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.

Related errors


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