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

  1. Inspect the lease in StoredCoreState: ensure expiresAtUnixMs is a positive safe integer and revocationEpoch is a non-negative safe integer before requesting a receipt.
  2. Renew/acquire a fresh lease via the control plane if the current one is expired or zeroed.
  3. Validate ackedSourceSeq comes from the durable event log as a number, not a serialized string.
  4. 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

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


AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10). Data as JSON: /api/errors/f3b24c8ea026ae78. Report an issue: GitHub.