{"record":{"id":"856c70dad516ed28","repo":"paperclipai/paperclip","slug":"warm-run-transition-requires-its-exact-approved-ru","errorCode":null,"errorMessage":"Warm run transition requires its exact approved runner artifact.","messagePattern":"Warm run transition requires its exact approved runner artifact\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/paperclip-runner/src/control-plane/durable-prp-control-plane.ts","lineNumber":1500,"sourceCode":"          options.connectionLeaseTtlMs < 60_000 ||\n          options.connectionLeaseTtlMs > 24 * 60 * 60 * 1_000))\n    ) {\n      throw new Error(\"Durable PRP control plane options are invalid.\");\n    }\n    this.#identity = structuredClone(options.identity);\n    this.#store = new DurableCoreStore(\n      options.stateDirectory,\n      options.identity,\n    );\n    this.#expectedRunnerVersion = options.expectedRunnerVersion;\n    this.#expectedRunnerDigest = options.expectedRunnerDigest;\n    const transition = this.#store.state.warmTransition;\n    if (\n      transition &&\n      (transition.receipt.runnerVersion !== options.expectedRunnerVersion ||\n        transition.receipt.runnerDigest !== options.expectedRunnerDigest)\n    ) {\n      throw new Error(\n        \"Warm run transition requires its exact approved runner artifact.\",\n      );\n    }\n    this.#onSemanticToolInput = options.onSemanticToolInput;\n    this.#onCommittedEvent = options.onCommittedEvent;\n    this.#beforeAuthenticatedConnection = options.beforeAuthenticatedConnection;\n    this.#onProtocolIntegrityError = options.onProtocolIntegrityError;\n    this.#connectionLeaseTtlMs = options.connectionLeaseTtlMs ?? 60_000;\n  }\n\n  get store(): DurablePrpControlPlaneStore {\n    return this.#store;\n  }\n\n  getCommand(commandId: string): DurableRecoveryCoreCommand | undefined {\n    return (\n      this.#store.state.commands.find(\n        (command) => command.commandId === commandId,","sourceCodeStart":1482,"sourceCodeEnd":1518,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/packages/paperclip-runner/src/control-plane/durable-prp-control-plane.ts#L1482-L1518","documentation":"When rehydrating/resuming a control plane with a stored warm transition, the caller must present the exact approved runner artifact the transition receipt recorded. If the supplied expectedRunnerVersion or expectedRunnerDigest differs from transition.receipt, the plane throws rather than resuming under a different binary.","triggerScenarios":"Calling the resume/start options with expectedRunnerVersion/expectedRunnerDigest that do not match the receipt stored in state.warmTransition — e.g. the runner was upgraded or rebuilt between shutdown and warm resume.","commonSituations":"Deploying a new runner version into a workspace with a pending warm transition; rebuilding the binary locally so its digest changed; pointing the resume at a different runner build than the one approved for the warm path.","solutions":["Pass the expectedRunnerVersion/expectedRunnerDigest recorded in state.warmTransition.receipt.","If the new runner version is intended, clear/complete the pending warm transition (cold start) instead of warm-resuming.","Verify the runner artifact digest (hash the binary) and correct the options values.","Re-approve the warm transition for the new artifact if your workflow supports re-approval."],"exampleFix":"// before\nresume({ expectedRunnerVersion: '1.2.0', expectedRunnerDigest: 'abc' });\n// after\nconst { receipt } = store.state.warmTransition;\nresume({ expectedRunnerVersion: receipt.runnerVersion, expectedRunnerDigest: receipt.runnerDigest });","handlingStrategy":"validation","validationCode":"const t = store.state.warmTransition;\nif (t && (options.expectedRunnerVersion !== t.receipt.runnerVersion || options.expectedRunnerDigest !== t.receipt.runnerDigest)) {\n  throw new Error('runner artifact does not match approved warm transition; cold start instead');\n}","typeGuard":null,"tryCatchPattern":"try {\n  await controlPlane.resume(options);\n} catch (e) {\n  if (e.message.includes('exact approved runner artifact')) {\n    console.error('runner binary changed since warm transition approval; perform a cold start or re-approve');\n    throw e;\n  }\n  throw e;\n}","preventionTips":["Compute expectedRunnerVersion/Digest from the receipt in stored state, not from the currently installed binary.","Pin the runner artifact version across a deploy cycle or plan a cold start on upgrade.","Record build digests in CI and pass them explicitly to resume options."],"tags":["digest-mismatch","state","warm-resume"],"backgroundTag":"checksum-mismatch","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"}