{"record":{"id":"f782bb150479cf28","repo":"paperclipai/paperclip","slug":"installing-or-updating-paperclip-requires-node-js","errorCode":null,"errorMessage":"Installing or updating Paperclip requires Node.js ${MINIMUM_NODE_VERSION} or newer (found ${process.version} at ${process.execPath}). Put a supported Node bin directory first on PATH and run 'npx paperclipai@latest install --yes' to re-pin an existing managed install.","messagePattern":"Installing or updating Paperclip requires Node\\.js (.+?) or newer \\(found (.+?) at (.+?)\\)\\. Put a supported Node bin directory first on PATH and run 'npx paperclipai@latest install --yes' to re-pin an existing managed install\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"cli/src/commands/install.ts","lineNumber":89,"sourceCode":"    for (const section of [\"dependencies\", \"optionalDependencies\", \"peerDependencies\"] as const) {\n      const dependencies = packageJson[section];\n      if (!dependencies || typeof dependencies !== \"object\") continue;\n      for (const dependencyName of Object.keys(dependencies)) {\n        if (dependencyName.startsWith(\"@paperclipai/\")) visit(dependencyName);\n      }\n    }\n    visiting.delete(packageName);\n    visited.add(packageName);\n    ordered.push(entry);\n  };\n\n  visit(\"@paperclipai/server\");\n  return ordered;\n}\n\nexport function assertSupportedNodeVersion(): void {\n  if (!isSupportedNodeVersion(process.versions.node)) {\n    throw new Error(`Installing or updating Paperclip requires Node.js ${MINIMUM_NODE_VERSION} or newer (found ${process.version} at ${process.execPath}). Put a supported Node bin directory first on PATH and run 'npx paperclipai@latest install --yes' to re-pin an existing managed install.`);\n  }\n}\n\nexport function resolveNpmInstallRequest(options: InstallOptions): {\n  spec: string;\n  channel: InstallChannel;\n} {\n  if (options.canary && options.version) throw new Error(\"Choose either --canary or --version, not both.\");\n  if (options.version) {\n    const version = options.version.trim();\n    if (!EXACT_VERSION_PATTERN.test(version)) {\n      throw new Error(`--version requires an exact published version, received '${options.version}'.`);\n    }\n    return { spec: version, channel: \"pinned\" };\n  }\n  return options.canary ? { spec: \"canary\", channel: \"canary\" } : { spec: \"latest\", channel: \"latest\" };\n}\n","sourceCodeStart":71,"sourceCodeEnd":107,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/cli/src/commands/install.ts#L71-L107","documentation":"This error is thrown by the runnerd codex transport when a PRP (provider run protocol) run rotation requires rotating an EXTERNAL authority epoch, but the transport options do not supply the callback functions needed to read and archive external runner state. The library refuses to guess identity rotation state and fails fast rather than proceeding without durable external state bookkeeping.","triggerScenarios":"A run-rotation path is taken where the runner's authority must be rotated under an external state owner, but the options object passed to the transport omitted `readRunnerState` or `archiveExternalRunnerState` (one or both are `undefined`).","commonSituations":"Constructing the transport with a partial options object after a refactor; wiring an internal/local-state runner without providing external state callbacks; version changes that added newly required callback fields to the options contract.","solutions":["Provide both `readRunnerState` and `archiveExternalRunnerState` callbacks in the transport options before enabling PRP run rotation.","If the runner manages state internally instead, arrange the rotation path to use the local authority epoch rotation (localStateOwner) rather than the external path.","Check the transport construction code for conditional spreading of options that drops these callbacks."],"exampleFix":"// before\nconst transport = new RunnerdCodexTransport({ archiveExternalRunnerState });\n// after\nconst transport = new RunnerdCodexTransport({\n  readRunnerState: async () => loadRunnerState(),\n  archiveExternalRunnerState: async () => archiveState(),\n});","handlingStrategy":"validation","validationCode":"if (!options.readRunnerState || !options.archiveExternalRunnerState) {\n  throw new Error(\"external state callbacks required for PRP run rotation\");\n}","typeGuard":"function hasExternalStateCallbacks(o: unknown): o is TransportOptions & {\n  readRunnerState: () => Promise<unknown>;\n  archiveExternalRunnerState: () => Promise<void>;\n} {\n  const x = o as TransportOptions;\n  return typeof x.readRunnerState === \"function\" && typeof x.archiveExternalRunnerState === \"function\";\n}","tryCatchPattern":"try {\n  await transport.start(params);\n} catch (e) {\n  if (e.message === \"native_runner_prp_run_rotation_unavailable\") {\n    // rebuild transport with full external-state callback set and retry\n  }\n}","preventionTips":["Always construct transport options through a factory that requires external-state callbacks together.","Add a startup assertion validating required callbacks before first use.","Review options objects after refactors for conditionally dropped fields."],"tags":["configuration","runner","state-rotation"],"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-14T05:17:10.506Z"}