{"record":{"id":"746c5318de593e4f","repo":"paperclipai/paperclip","slug":"warm-run-identity-rotation-requires-a-durable-tran","errorCode":null,"errorMessage":"Warm run identity rotation requires a durable transition receipt.","messagePattern":"Warm run identity rotation requires a durable transition receipt\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/paperclip-runner/src/control-plane/durable-prp-control-plane.ts","lineNumber":1670,"sourceCode":"        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);\n      }\n      return;\n    }\n    if (\n      this.#store.state.commands.some(\n        (command) => command.type === \"run.attach\",\n      )\n    ) {\n      throw new Error(\n        \"Warm run identity rotation requires a durable transition receipt.\",\n      );\n    }\n    if (\n      !Object.values(identity).every(\n        (value) => typeof value === \"string\" && stableIdPattern.test(value),\n      ) ||\n      identity.runnerInstanceId !== this.#identity.runnerInstanceId ||\n      identity.environmentLeaseId !== this.#identity.environmentLeaseId ||\n      identity.normalizedSessionId !== this.#identity.normalizedSessionId ||\n      identity.runId === this.#identity.runId ||\n      this.#store.state.commands.some((command) => command.status === \"pending\")\n    ) {\n      throw new Error(\"Durable PRP run identity rotation is invalid.\");\n    }\n    this.disconnectActiveRunner();\n    const leases = Object.fromEntries(\n      Object.entries(this.#store.state.leases).map(([key, lease]) => [","sourceCodeStart":1652,"sourceCodeEnd":1688,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/packages/paperclip-runner/src/control-plane/durable-prp-control-plane.ts#L1652-L1688","documentation":"rotateRunIdentity() was called with no warm transition and no completed transition receipt in the durable store, while the command log already contains a \"run.attach\" command. Identity rotation without a durable transition receipt is only allowed on a fresh (never-attached) store; an existing run.attach command means the run was previously attached, and its identity can only rotate through a proper warm transition with a receipt.","triggerScenarios":"Calling rotateRunIdentity(identity) on a store that has recorded a run.attach command but whose warmTransition/completedWarmTransition entries are absent (e.g. they were compacted, or the transition was never performed and the caller attempts a cold rotation over an attached run).","commonSituations":"Store compaction or reset dropped transition records while command history remained; calling cold-start rotation on a resumed run; migration of a store from a version that did not persist transitions.","solutions":["Perform the warm transition flow first so a durable receipt exists, then rotate via rotateRunIdentity.","If the store should be fresh, start from a new store/identity instead of rotating an attached one.","Recover from a backup/snapshot of the store that includes the transition receipt.","Audit why warmTransition and completedWarmTransition are both absent while run.attach history exists (compaction bug) before proceeding."],"exampleFix":"// before\ncontrolPlane.rotateRunIdentity(newIdentity); // store already has run.attach, no receipt\n// after\nawait performWarmTransition(newIdentity); // creates durable receipt\ncontrolPlane.rotateRunIdentity(newIdentity, attachTemplate);","handlingStrategy":"validation","validationCode":"const hasAttach = store.state.commands.some((c) => c.type === \"run.attach\");\nconst hasReceipt = store.state.warmTransition != null || store.state.completedWarmTransition != null;\nif (hasAttach && !hasReceipt) {\n  throw new Error(\"Cannot cold-rotate: run attached without durable receipt; use warm transition recovery.\");\n}","typeGuard":null,"tryCatchPattern":"try {\n  controlPlane.rotateRunIdentity(identity);\n} catch (err) {\n  if (err instanceof Error && err.message.includes(\"requires a durable transition receipt\")) {\n    await performWarmTransition(identity); // create receipt, then rotate\n    controlPlane.rotateRunIdentity(identity, attachTemplate);\n  } else throw err;\n}","preventionTips":["Only call rotateRunIdentity cold on a store that never recorded a run.attach command.","Never compact or prune transition records while run.attach command history remains.","Route all identity changes for attached runs through the warm transition flow."],"tags":["state-machine","durability","warm-transition","identity-rotation"],"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"}