paperclipai/paperclip · error
Warm run transition binding is invalid.
Error message
Warm run transition binding is invalid.
What it means
warmTransitionReceipt builds the wire receipt binding a warm-run transition to its lease, identity and sequence numbers. It throws when any binding component is structurally invalid: missing lease/identity/boundary data, non-positive or missing lease expiry, or a missing/negative/non-integer revocationEpoch or ackedSourceSeq.
Source
Thrown at packages/paperclip-runner/src/control-plane/durable-prp-control-plane.ts:453
boundary.identity.runnerInstanceId !== identity.runnerInstanceId ||
boundary.identity.environmentLeaseId !== identity.environmentLeaseId ||
boundary.identity.normalizedSessionId !== identity.normalizedSessionId ||
command.type !== "run.attach" ||
result.status !== "completed" ||
result.commandId !== command.commandId ||
result.commandType !== command.type ||
result.controllerSeq !== command.controllerSeq ||
!stableIdPattern.test(runnerVersion) ||
!runnerDigestPattern.test(runnerDigest) ||
!stableIdPattern.test(lease.leaseId) ||
!Number.isSafeInteger(lease.expiresAtUnixMs) ||
lease.expiresAtUnixMs <= 0 ||
!Number.isSafeInteger(lease.revocationEpoch) ||
lease.revocationEpoch < 0 ||
!Number.isSafeInteger(ackedSourceSeq) ||
ackedSourceSeq < 0
) {
throw new Error("Warm run transition binding is invalid.");
}
const { status: _status, result: _result, ...wire } = command;
const body = {
schema: "paperclip.runner.warm-transition.v1" as const,
oldIdentity: structuredClone(identity),
newIdentity: structuredClone(boundary.identity),
commandId: command.commandId,
controllerSeq: command.controllerSeq,
// Rust's closed Command representation serializes these optional fields.
commandFingerprint: canonicalDigest({
...wire,
deadlineAt: null,
precondition: null,
}),
resultDigest: canonicalDigest(result),
oldAckedSourceSeq: ackedSourceSeq,
connection: structuredClone(boundary.connection),
runnerVersion,View on GitHub (pinned to 01ad858492)
Solutions
- Inspect the lease in StoredCoreState: ensure expiresAtUnixMs is a positive safe integer and revocationEpoch is a non-negative safe integer before requesting a receipt.
- Renew/acquire a fresh lease via the control plane if the current one is expired or zeroed.
- Validate ackedSourceSeq comes from the durable event log as a number, not a serialized string.
- If the state file is corrupt, restore from the last committed candidate rather than patching fields ad hoc.
Example fix
// before
receipt({ lease: { revocationEpoch: null, expiresAtUnixMs: 0 }, ackedSourceSeq: '12' });
// after
receipt({ lease: { revocationEpoch: 3, expiresAtUnixMs: Date.now() + 30_000 }, ackedSourceSeq: 12 }); Defensive patterns
Strategy: validation
Validate before calling
function bindingValid(lease, ackedSourceSeq) {
return Number.isSafeInteger(lease?.expiresAtUnixMs) && lease.expiresAtUnixMs > 0 &&
Number.isSafeInteger(lease?.revocationEpoch) && lease.revocationEpoch >= 0 &&
Number.isSafeInteger(ackedSourceSeq) && ackedSourceSeq >= 0;
} Type guard
function isValidLease(l: unknown): l is { expiresAtUnixMs: number; revocationEpoch: number } {
const x = l as any;
return !!x && Number.isSafeInteger(x.expiresAtUnixMs) && x.expiresAtUnixMs > 0 &&
Number.isSafeInteger(x.revocationEpoch) && x.revocationEpoch >= 0;
} Try / catch
try {
const receipt = warmTransitionReceipt(binding);
} catch (e) {
if (e.message.includes('transition binding is invalid')) {
const fresh = await acquireFreshLease();
return warmTransitionReceipt({ ...binding, lease: fresh });
}
throw e;
} Prevention
- Validate leases at load time so bad state fails at the boundary, not deep in receipt building.
- Keep sequence counters as numbers end-to-end; avoid string coercion through JSON round-trips.
- Renew leases proactively before expiry instead of using zeroed/expired values.
When it happens
Trigger: Calling warmTransitionReceipt (directly or via expected/isStoredCoreState/receipt callers) with a lease whose expiresAtUnixMs is 0/negative/NaN, a lease without an integer revocationEpoch >= 0, or an ackedSourceSeq that is not a safe non-negative integer.
Common situations: State file corrupted or hand-edited so the lease lost its expiry; a fresh/empty lease object constructed before initialization; sequence counters serialized as strings or null after a schema change.
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
- opencode_run_attach_invalid
- Provider drain state is malformed.
- Invalid Discord correction binding
- Invalid status '${String(rawStatus)}'. Must be one of: ${PLU
- "tool" is required and must be a string
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/f3b24c8ea026ae78.
Report an issue: GitHub.