{"record":{"id":"b03385c67e8f4673","repo":"paperclipai/paperclip","slug":"warm-run-transition-target-conflicts-with-its-dura","errorCode":null,"errorMessage":"Warm run transition target conflicts with its durable receipt.","messagePattern":"Warm run transition target conflicts with its durable receipt\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/paperclip-runner/src/control-plane/durable-prp-control-plane.ts","lineNumber":1643,"sourceCode":"      if (\n        runAttachTemplate !== undefined &&\n        canonicalJson(runAttachTemplate) !== canonicalJson(template)\n      ) {\n        throw new Error(\n          \"Completed warm transition template conflicts with its exact command.\",\n        );\n      }\n      return;\n    }\n    const transition = this.#store.state.warmTransition;\n    if (transition) {\n      if (transition.phase === \"awaiting_result\")\n        throw new Error(\"Warm transition result is not yet authenticated.\");\n      if (\n        canonicalJson(identity) !==\n        canonicalJson(transition.receipt.newIdentity)\n      ) {\n        throw new Error(\n          \"Warm run transition target conflicts with its durable receipt.\",\n        );\n      }\n      // The new authenticated peer, not an attach-result observer, owns the\n      // activation boundary. Keep the old credential and command replay lane.\n      if (runAttachTemplate !== undefined) {\n        const { paperclipNextAuthority: _boundary, ...expectedTemplate } =\n          transition.command.payload;\n        if (\n          canonicalJson(runAttachTemplate) !== canonicalJson(expectedTemplate)\n        ) {\n          throw new Error(\n            \"Warm run transition template conflicts with its exact command.\",\n          );\n        }\n        const candidate = structuredClone(this.#store.state);\n        candidate.runAttachTemplate = structuredClone(runAttachTemplate);\n        this.#store.commit(candidate);","sourceCodeStart":1625,"sourceCodeEnd":1661,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/packages/paperclip-runner/src/control-plane/durable-prp-control-plane.ts#L1625-L1661","documentation":"rotateRunIdentity() was called with an identity whose canonical JSON does not equal the newIdentity recorded in the durable warm transition receipt. The transition receipt is the authoritative record of the target identity, so any mismatched rotation attempt is rejected to prevent hijacking the transition to a different identity.","triggerScenarios":"Calling rotateRunIdentity(identity) while an authenticated warmTransition exists, passing an identity with different runnerInstanceId, environmentLeaseId, normalizedSessionId, or runId than transition.receipt.newIdentity.","commonSituations":"The caller regenerated runId between transition initiation and rotation; stale cached identity from a previous process; two supervisors racing with different target identities; config change altered a lease/session id mid-transition.","solutions":["Use exactly the identity from warmTransition.receipt.newIdentity when calling rotateRunIdentity during a transition.","If a different target identity is truly wanted, cancel/reset the existing transition via the recovery path first.","Persist and reload the receipt identity rather than recomputing it from local config.","Log canonicalJson(identity) vs transition.receipt.newIdentity to find the differing field."],"exampleFix":"// before\ncontrolPlane.rotateRunIdentity({ ...newIdentity, runId: freshRunId() }); // conflicts with receipt\n// after\nconst receipt = store.state.warmTransition.receipt;\ncontrolPlane.rotateRunIdentity(structuredClone(receipt.newIdentity));","handlingStrategy":"validation","validationCode":"const t = store.state.warmTransition;\nif (t && canonicalJson(identity) !== canonicalJson(t.receipt.newIdentity)) {\n  identity = structuredClone(t.receipt.newIdentity); // adopt receipt identity\n}","typeGuard":"function matchesReceipt(identity: DurableRecoveryIdentity, t: { receipt: { newIdentity: DurableRecoveryIdentity } }): boolean {\n  return canonicalJson(identity) === canonicalJson(t.receipt.newIdentity);\n}","tryCatchPattern":"try {\n  controlPlane.rotateRunIdentity(identity);\n} catch (err) {\n  if (err instanceof Error && err.message.includes(\"target conflicts with its durable receipt\")) {\n    controlPlane.rotateRunIdentity(structuredClone(store.state.warmTransition.receipt.newIdentity));\n  } else throw err;\n}","preventionTips":["Always source the rotation identity from the transition receipt, never from freshly generated ids.","Persist the target identity at transition initiation and reload it after restarts.","Ensure only one supervisor issues rotations for a given run."],"tags":["state-machine","identity","warm-transition","fail-closed"],"backgroundTag":"invalid-state-transition","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"}