{"record":{"id":"57b8b20232d3d6c7","repo":"paperclipai/paperclip","slug":"warm-transition-bootstrap-snapshot-proof-is-not-au","errorCode":null,"errorMessage":"Warm transition bootstrap snapshot proof is not authorized.","messagePattern":"Warm transition bootstrap snapshot proof is not authorized\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/paperclip-runner/src/control-plane/durable-prp-control-plane.ts","lineNumber":1784,"sourceCode":"    this.#store.assertWritable();\n    const pending = input.runnerState.warmTransition;\n    const proof = warmTransitionRecoveryProof({\n      controlPlaneState: this.#store.state,\n      runnerState: input.runnerState,\n      expectedNewIdentity: (isRecord(pending) && isRecord(pending.receipt)\n        ? pending.receipt.newIdentity\n        : null) as DurableRecoveryIdentity,\n      expectedRunnerVersion: this.#expectedRunnerVersion,\n      expectedRunnerDigest: this.#expectedRunnerDigest,\n    });\n    if (\n      !proof ||\n      input.transitionId !== proof.transition.receipt.transitionId ||\n      !Number.isInteger(ttlMs) ||\n      ttlMs < 1_000 ||\n      ttlMs > 60_000\n    ) {\n      throw new Error(\n        \"Warm transition bootstrap snapshot proof is not authorized.\",\n      );\n    }\n    const { transition, original, requested } = proof;\n    const ticket = `bootstrap_${randomUUID()}`;\n    const material = credentialMaterial(ticket);\n    const expiresAtUnixMs = Math.min(\n      Date.now() + ttlMs,\n      original.expiresAtUnixMs,\n    );\n    const candidate = structuredClone(this.#store.state);\n    candidate.schema = transitionCoreStateSchema;\n    candidate.warmTransition = structuredClone(transition);\n    candidate.tickets[material.credentialId] = {\n      recordId: `bootstrap_ticket_${randomUUID()}`,\n      credentialId: material.credentialId,\n      authKeyDigest: `sha256:${material.authKey.toString(\"hex\")}`,\n      identity: structuredClone(requested),","sourceCodeStart":1766,"sourceCodeEnd":1802,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/packages/paperclip-runner/src/control-plane/durable-prp-control-plane.ts#L1766-L1802","documentation":"This error is thrown by the warm-transition bootstrap authorization check in the durable PRP control plane. Before issuing a bootstrap snapshot for a warm run transition, the code verifies that a proof exists, that the requested transitionId matches the transitionId embedded in the proof's transition receipt, and that the requested TTL is an integer between 1 and 60 seconds. If any precondition fails, the bootstrap request is rejected as unauthorized.","triggerScenarios":"Calling the warm-transition bootstrap path with proof=null/undefined, with a transitionId that differs from proof.transition.receipt.transitionId, or with ttlMs that is not an integer or is <1000 or >60000.","commonSituations":"Passing a stale proof from a previous transition, constructing the proof manually with a mismatched transitionId, misconfiguring TTL in milliseconds vs seconds (e.g. passing 120000 thinking it is seconds), or a race where a newer transition invalidated the cached proof.","solutions":["Ensure the proof object passed to the bootstrap call is the exact proof issued for the same transitionId, not one from an earlier transition.","Set ttlMs to an integer number of milliseconds in the 1000-60000 range (1-60 seconds).","Re-fetch/rebuild the transition proof if the transition was re-created after a restart or retry.","Log proof.transition.receipt.transitionId and input.transitionId side by side to find the mismatch."],"exampleFix":"// before\nawait controlPlane.warmTransitionBootstrap({ transitionId: oldId, proof: staleProof, ttlMs: 120000 });\n// after\nawait controlPlane.warmTransitionBootstrap({ transitionId: proof.transition.receipt.transitionId, proof, ttlMs: 30000 });","handlingStrategy":"validation","validationCode":"function canBootstrap(input) {\n  const ttlMs = input.ttlMs;\n  return Boolean(input.proof) &&\n    input.transitionId === input.proof.transition.receipt.transitionId &&\n    Number.isInteger(ttlMs) && ttlMs >= 1000 && ttlMs <= 60000;\n}\nif (!canBootstrap(input)) throw new Error(\"invalid warm transition bootstrap input\");","typeGuard":"function hasValidProof(input) {\n  return input.proof != null && input.transitionId === input.proof.transition.receipt.transitionId;\n}","tryCatchPattern":"try {\n  await controlPlane.warmTransitionBootstrap(input);\n} catch (err) {\n  if (err.message.includes(\"not authorized\")) {\n    // refetch proof and re-validate ttlMs\n  } else throw err;\n}","preventionTips":["Always derive the input transitionId from the proof object itself.","Keep ttlMs constants within 1000-60000 ms.","Refetch proofs after any transition re-creation rather than caching across retries."],"tags":["security","validation","warm-transition"],"backgroundTag":"invalid-argument-value","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"}