paperclipai/paperclip · error
Warm transition recovery requires its explicit one-use boots
Error message
Warm transition recovery requires its explicit one-use bootstrap capability.
What it means
issueBootstrapTicket() refuses to mint a fresh bootstrap capability while a warmTransition is present in the durable store. During warm-transition recovery, the only sanctioned entry is the explicit one-use bootstrap capability tied to that transition; issuing a new generic ticket would bypass the authenticated handoff, so the call is rejected.
Source
Thrown at packages/paperclip-runner/src/control-plane/durable-prp-control-plane.ts:1731
throw new Error("Durable PRP run attachment template is invalid.");
}
const existing = this.#store.state.runAttachTemplate;
if (
existing !== undefined &&
existing !== null &&
canonicalJson(existing) !== canonicalJson(runAttachTemplate)
) {
throw new Error("Durable PRP run attachment template conflicts.");
}
if (existing !== undefined && existing !== null) return;
this.#store.state.runAttachTemplate = structuredClone(runAttachTemplate);
this.#store.save();
}
issueBootstrapTicket(ttlMs = 5_000): string {
this.#store.assertWritable();
if (this.#store.state.warmTransition) {
throw new Error(
"Warm transition recovery requires its explicit one-use bootstrap capability.",
);
}
if (!Number.isInteger(ttlMs) || ttlMs < 1_000 || ttlMs > 60_000) {
throw new Error("Durable PRP bootstrap TTL is invalid.");
}
this.#pruneCredentials();
const ticket = `bootstrap_${randomUUID()}`;
const material = credentialMaterial(ticket);
const expiresAtUnixMs = Date.now() + ttlMs;
this.#store.state.tickets[material.credentialId] = {
recordId: `bootstrap_ticket_${randomUUID()}`,
credentialId: material.credentialId,
authKeyDigest: `sha256:${material.authKey.toString("hex")}`,
identity: structuredClone(this.#identity),
runnerVersion: this.#expectedRunnerVersion,
runnerDigest: this.#expectedRunnerDigest,
expiresAt: new Date(expiresAtUnixMs).toISOString(),View on GitHub (pinned to 01ad858492)
Solutions
- Use the explicit one-use bootstrap capability issued for the warm transition instead of calling issueBootstrapTicket().
- Check store.state.warmTransition before bootstrapping and branch to the transition-recovery path when present.
- If the transition is abandoned and truly unrecoverable, clear the warmTransition state through the documented recovery/reset flow first, then issue a ticket.
- Do not delete transition state ad hoc (e.g. by editing the store) — it exists to authenticate the new identity.
Example fix
// before
const ticket = controlPlane.issueBootstrapTicket(); // throws mid-transition
// after
if (store.state.warmTransition) {
await recoverViaTransitionCapability();
} else {
const ticket = controlPlane.issueBootstrapTicket();
} Defensive patterns
Strategy: type-guard
Validate before calling
if (store.state.warmTransition) {
await recoverViaTransitionCapability(); // use the transition's one-use bootstrap capability
} else {
controlPlane.issueBootstrapTicket();
} Type guard
function inWarmTransition(state: { warmTransition?: unknown }): boolean {
return state.warmTransition != null;
} Try / catch
try {
ticket = controlPlane.issueBootstrapTicket();
} catch (err) {
if (err instanceof Error && err.message.includes("one-use bootstrap capability")) {
ticket = await useTransitionBootstrapCapability(); // sanctioned path
} else throw err;
} Prevention
- Always branch bootstrap logic on the presence of warmTransition state.
- Persist and reuse the transition's bootstrap capability across restarts.
- Never clear transition state to unblock ticket issuance; use the documented recovery flow.
When it happens
Trigger: Calling issueBootstrapTicket() while store.state.warmTransition is set — e.g. an operator restarts the runner and the bootstrap code unconditionally issues a new ticket instead of using the transition's existing capability.
Common situations: Crash/restart during a warm transition leaves the transition state behind and generic bootstrap logic runs; a second runner instance tries to bootstrap into a run mid-handoff; recovery scripts don't check for an active transition.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Completed warm transition template conflicts with its exact
- Warm transition result is not yet authenticated.
- Warm run transition target conflicts with its durable receip
- Warm run transition template conflicts with its exact comman
- Warm run identity rotation requires a durable transition rec
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/d2cc3d8646066c02.
Report an issue: GitHub.