{"record":{"id":"d6d6123118605583","repo":"paperclipai/paperclip","slug":"native-adopted-runner-state-directory-required","errorCode":"native_adopted_runner_state_directory_required","errorMessage":"native_adopted_runner_state_directory_required","messagePattern":"native_adopted_runner_state_directory_required","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/paperclip-runner/src/live/runnerd-codex-transport.ts","lineNumber":3209,"sourceCode":"  #failure: Error | null = null;\n  readonly #failureSignal: Promise<never>;\n  #rejectFailureSignal!: (error: Error) => void;\n  #runnerRecoveryInProgress = false;\n  #startupComplete = false;\n  #startupFailureCode = \"native_runner_process_exited\";\n  #controlPlaneCheckpoint:\n    ((settlement: \"settled\" | \"unsettled\") => Promise<void> | void) | null =\n    null;\n  #controlPlaneRelease: (() => Promise<void> | void) | null = null;\n  #nextTraceDebugSequence = 1;\n  #traceRehydrationSpoolOverflow = false;\n  #pendingTraceRehydrations: PendingTraceRehydration[] = [];\n  #pendingDriverTraceInterpretations: PendingDriverTraceInterpretation[] = [];\n  readonly #bridgedRuntimeInputs = new Map<string, { durableTurnId: string }>();\n\n  constructor(readonly options: CapabilityRunnerdCodexTransportOptions) {\n    if (options.adoptExistingRunner && !options.stateDirectory?.trim()) {\n      throw new Error(\"native_adopted_runner_state_directory_required\");\n    }\n    if (options.provider === \"acpx\" && options.acpxAgent === \"pi\") {\n      throw new Error(\"The Pi ACPX profile is not available\");\n    }\n    this.#failureSignal = new Promise<never>((_resolve, reject) => {\n      this.#rejectFailureSignal = reject;\n    });\n    // Failure is also observed by request/notification paths. Register an\n    // internal handler so a process exit after the owner has closed the\n    // session cannot become an unhandled process-level rejection.\n    void this.#failureSignal.catch(() => undefined);\n    this.#ownsRoot = options.stateDirectory === undefined;\n    this.#turnId = options.resumeActiveTurnId ?? \"\";\n    this.#root =\n      options.stateDirectory ??\n      mkdtempSync(resolve(tmpdir(), \"paperclip-runner-lab-prp-\"));\n    if (options.resumeDynamicTools !== undefined) {\n      this.#authorizedTools = authorizedToolSetForProvider(options.provider, [","sourceCodeStart":3191,"sourceCodeEnd":3227,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/packages/paperclip-runner/src/live/runnerd-codex-transport.ts#L3191-L3227","documentation":"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.","triggerScenarios":"new CapabilityRunnerdCodexTransport({ adoptExistingRunner: true, stateDirectory: undefined }) or stateDirectory: '' / '   ' (whitespace-only is also rejected).","commonSituations":"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.","solutions":["Pass a non-empty stateDirectory when adoptExistingRunner is true, e.g. stateDirectory: '/var/lib/paperclip/runner'.","Set the env/config value feeding stateDirectory (e.g. PAPERCLIP_RUNNER_STATE_DIR) before constructing the transport.","If not adopting an existing runner, set adoptExistingRunner: false so the check is skipped.","Trim/validate the value at config-load time to fail early with a clearer message."],"exampleFix":"// before\nnew CapabilityRunnerdCodexTransport({ adoptExistingRunner: true });\n// after\nnew CapabilityRunnerdCodexTransport({ adoptExistingRunner: true, stateDirectory: process.env.RUNNER_STATE_DIR ?? '/var/lib/paperclip/runner' });","handlingStrategy":"validation","validationCode":"if (opts.adoptExistingRunner && !(opts.stateDirectory ?? '').trim()) {\n  throw new Error('stateDirectory is required when adoptExistingRunner is true');\n}","typeGuard":"const canAdopt = (o: { adoptExistingRunner?: boolean; stateDirectory?: string }): o is Required<typeof o> =>\n  !o.adoptExistingRunner || Boolean(o.stateDirectory?.trim());","tryCatchPattern":"try {\n  transport = new CapabilityRunnerdCodexTransport(options);\n} catch (err) {\n  if ((err as Error).message === 'native_adopted_runner_state_directory_required') {\n    options.stateDirectory = resolveStateDir();\n    transport = new CapabilityRunnerdCodexTransport(options);\n  } else throw err;\n}","preventionTips":["Validate transport options at config-load time","Give stateDirectory a safe default in the environment setup","Trim and check string config values before constructing transports"],"tags":["config","constructor","validation","runner"],"backgroundTag":"missing-required-config-field","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}