{"record":{"id":"23a3109c2162d0b3","repo":"paperclipai/paperclip","slug":"completed-warm-transition-template-conflicts-with","errorCode":null,"errorMessage":"Completed warm transition template conflicts with its exact command.","messagePattern":"Completed warm transition template conflicts with its exact command\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/paperclip-runner/src/control-plane/durable-prp-control-plane.ts","lineNumber":1629,"sourceCode":"  rotateRunIdentity(\n    identity: DurableRecoveryIdentity,\n    runAttachTemplate?: Record<string, unknown>,\n  ): void {\n    if (this.#protocolIntegrityError !== null)\n      throw this.#protocolIntegrityError;\n    const completed = this.#store.state.completedWarmTransition;\n    if (\n      completed &&\n      canonicalJson(identity) === canonicalJson(this.#identity) &&\n      canonicalJson(identity) === canonicalJson(completed.receipt.newIdentity)\n    ) {\n      const { paperclipNextAuthority: _boundary, ...template } =\n        completed.command.payload;\n      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","sourceCodeStart":1611,"sourceCodeEnd":1647,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/packages/paperclip-runner/src/control-plane/durable-prp-control-plane.ts#L1611-L1647","documentation":"rotateRunIdentity() was called on a control plane whose warm transition already completed durably (completedWarmTransition is set). For an idempotent replay the caller re-passed a runAttachTemplate whose canonical JSON differs from the template recorded in the completed transition's command payload (after stripping the paperclipNextAuthority boundary field). The library fails closed because the replay does not match the exact original command.","triggerScenarios":"Calling rotateRunIdentity(identity, runAttachTemplate) a second time with the same identity after the transition completed, but with a modified runAttachTemplate (different keys, values, ordering-insensitive but content-different) than the one used in the original, already-completed transition.","commonSituations":"Caller code recomputes the attach template from drifted config (e.g. provider settings changed between retries); a serialization layer adds or drops fields; a retry path passes an updated template while the transition already succeeded on the first attempt.","solutions":["Do not pass runAttachTemplate on idempotent replays of a completed warm transition; call rotateRunIdentity(identity) with the template omitted.","Compare your template against the completed transition's command payload (minus paperclipNextAuthority) and reuse the exact original object.","If the template genuinely must change, treat it as a new rotation: reset the transition state per your recovery procedure instead of replaying the completed one.","Log canonicalJson(runAttachTemplate) and the completed command payload to identify which field drifted before retrying."],"exampleFix":"// before\ncontrolPlane.rotateRunIdentity(newIdentity, { provider: { name: \"claude\" }, model: \"opus\" }); // replay after completion, template drifted\n// after\ncontrolPlane.rotateRunIdentity(newIdentity); // idempotent replay without re-asserting the template","handlingStrategy":"try-catch","validationCode":"// before replaying a completed transition\nconst completed = store.state.completedWarmTransition;\nif (completed && runAttachTemplate !== undefined) {\n  const { paperclipNextAuthority: _b, ...expected } = completed.command.payload;\n  if (canonicalJson(runAttachTemplate) !== canonicalJson(expected)) {\n    runAttachTemplate = undefined; // omit on idempotent replay\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  controlPlane.rotateRunIdentity(identity, runAttachTemplate);\n} catch (err) {\n  if (err instanceof Error && err.message.includes(\"Completed warm transition template conflicts\")) {\n    controlPlane.rotateRunIdentity(identity); // replay without re-asserting template\n  } else throw err;\n}","preventionTips":["Omit runAttachTemplate on retries of an already-completed warm transition.","Keep the template immutable (frozen) once a transition is initiated.","Strip volatile fields from templates before passing them to rotation."],"tags":["state-machine","idempotency","warm-transition","fail-closed"],"backgroundTag":"conflicting-config-options","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"}