{"record":{"id":"ad89f61d3a5dd50e","repo":"paperclipai/paperclip","slug":"warm-transition-result-is-not-yet-authenticated","errorCode":null,"errorMessage":"Warm transition result is not yet authenticated.","messagePattern":"Warm transition result is not yet authenticated\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/paperclip-runner/src/control-plane/durable-prp-control-plane.ts","lineNumber":1638,"sourceCode":"      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\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.\",","sourceCodeStart":1620,"sourceCodeEnd":1656,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/packages/paperclip-runner/src/control-plane/durable-prp-control-plane.ts#L1620-L1656","documentation":"rotateRunIdentity() was invoked while the durable warmTransition exists in phase \"awaiting_result\", meaning the transition handshake has been issued but its authenticated result (receipt) has not yet been written to the durable store. Identity rotation cannot proceed on an unauthenticated transition, so the call is rejected.","triggerScenarios":"Calling rotateRunIdentity() between the moment a warm transition was initiated and the moment its result/receipt was durably committed — e.g. a runner or supervisor retries rotation immediately after triggering the transition without waiting for the run.attach acknowledgment to settle.","commonSituations":"Race between a transition initiator and a rotation retry loop; a crash/restart leaves the transition mid-handshake and the recovery code calls rotateRunIdentity before the peer's result lands; polling logic doesn't check transition.phase first.","solutions":["Wait until the warm transition advances past \"awaiting_result\" (result authenticated durably) before calling rotateRunIdentity.","Poll store state / listen for the transition-completion event instead of retrying rotation on a fixed timer.","If the transition is permanently stuck in awaiting_result, use the recovery procedure (bootstrap ticket path) to clear it rather than forcing rotation.","Guard the call site: only rotate when warmTransition is undefined or its phase is not \"awaiting_result\"."],"exampleFix":"// before\nif (state.warmTransition) controlPlane.rotateRunIdentity(identity); // may hit awaiting_result\n// after\nif (!state.warmTransition || state.warmTransition.phase !== \"awaiting_result\") {\n  controlPlane.rotateRunIdentity(identity);\n} else {\n  await waitForTransitionResult();\n}","handlingStrategy":"retry","validationCode":"const t = store.state.warmTransition;\nconst canRotate = !t || t.phase !== \"awaiting_result\";\nif (!canRotate) await waitForTransitionResult();","typeGuard":"function canRotate(state: { warmTransition?: { phase: string } | null }): boolean {\n  return !state.warmTransition || state.warmTransition.phase !== \"awaiting_result\";\n}","tryCatchPattern":"try {\n  controlPlane.rotateRunIdentity(identity);\n} catch (err) {\n  if (err instanceof Error && err.message.includes(\"not yet authenticated\")) {\n    await waitForTransitionResult(); // then retry once\n    controlPlane.rotateRunIdentity(identity);\n  } else throw err;\n}","preventionTips":["Check warmTransition.phase before every rotation call.","Drive rotation off the transition-settled event, not fixed timers.","Add exponential backoff for rotation retries around a transition handshake."],"tags":["state-machine","race-condition","warm-transition","retry"],"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-14T05:17:10.506Z"}