paperclipai/paperclip · error
Warm transition bootstrap snapshot proof is not authorized.
Error message
Warm transition bootstrap snapshot proof is not authorized.
What it means
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.
Source
Thrown at packages/paperclip-runner/src/control-plane/durable-prp-control-plane.ts:1784
this.#store.assertWritable();
const pending = input.runnerState.warmTransition;
const proof = warmTransitionRecoveryProof({
controlPlaneState: this.#store.state,
runnerState: input.runnerState,
expectedNewIdentity: (isRecord(pending) && isRecord(pending.receipt)
? pending.receipt.newIdentity
: null) as DurableRecoveryIdentity,
expectedRunnerVersion: this.#expectedRunnerVersion,
expectedRunnerDigest: this.#expectedRunnerDigest,
});
if (
!proof ||
input.transitionId !== proof.transition.receipt.transitionId ||
!Number.isInteger(ttlMs) ||
ttlMs < 1_000 ||
ttlMs > 60_000
) {
throw new Error(
"Warm transition bootstrap snapshot proof is not authorized.",
);
}
const { transition, original, requested } = proof;
const ticket = `bootstrap_${randomUUID()}`;
const material = credentialMaterial(ticket);
const expiresAtUnixMs = Math.min(
Date.now() + ttlMs,
original.expiresAtUnixMs,
);
const candidate = structuredClone(this.#store.state);
candidate.schema = transitionCoreStateSchema;
candidate.warmTransition = structuredClone(transition);
candidate.tickets[material.credentialId] = {
recordId: `bootstrap_ticket_${randomUUID()}`,
credentialId: material.credentialId,
authKeyDigest: `sha256:${material.authKey.toString("hex")}`,
identity: structuredClone(requested),View on GitHub (pinned to 01ad858492)
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.
Example fix
// before
await controlPlane.warmTransitionBootstrap({ transitionId: oldId, proof: staleProof, ttlMs: 120000 });
// after
await controlPlane.warmTransitionBootstrap({ transitionId: proof.transition.receipt.transitionId, proof, ttlMs: 30000 }); Defensive patterns
Strategy: validation
Validate before calling
function canBootstrap(input) {
const ttlMs = input.ttlMs;
return Boolean(input.proof) &&
input.transitionId === input.proof.transition.receipt.transitionId &&
Number.isInteger(ttlMs) && ttlMs >= 1000 && ttlMs <= 60000;
}
if (!canBootstrap(input)) throw new Error("invalid warm transition bootstrap input"); Type guard
function hasValidProof(input) {
return input.proof != null && input.transitionId === input.proof.transition.receipt.transitionId;
} Try / catch
try {
await controlPlane.warmTransitionBootstrap(input);
} catch (err) {
if (err.message.includes("not authorized")) {
// refetch proof and re-validate ttlMs
} else throw err;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- sandbox runtime asset key is not a simple path segment: ${ke
- Invalid GitHub launcher run ID
- Unexpected trusted viewer asset
- Public attempt must contain the read-only public chat projec
- Public replay contains private provider metadata
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/57b8b20232d3d6c7.
Report an issue: GitHub.