{"record":{"id":"f3b24c8ea026ae78","repo":"paperclipai/paperclip","slug":"warm-run-transition-binding-is-invalid","errorCode":null,"errorMessage":"Warm run transition binding is invalid.","messagePattern":"Warm run transition binding is invalid\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/paperclip-runner/src/control-plane/durable-prp-control-plane.ts","lineNumber":453,"sourceCode":"    boundary.identity.runnerInstanceId !== identity.runnerInstanceId ||\n    boundary.identity.environmentLeaseId !== identity.environmentLeaseId ||\n    boundary.identity.normalizedSessionId !== identity.normalizedSessionId ||\n    command.type !== \"run.attach\" ||\n    result.status !== \"completed\" ||\n    result.commandId !== command.commandId ||\n    result.commandType !== command.type ||\n    result.controllerSeq !== command.controllerSeq ||\n    !stableIdPattern.test(runnerVersion) ||\n    !runnerDigestPattern.test(runnerDigest) ||\n    !stableIdPattern.test(lease.leaseId) ||\n    !Number.isSafeInteger(lease.expiresAtUnixMs) ||\n    lease.expiresAtUnixMs <= 0 ||\n    !Number.isSafeInteger(lease.revocationEpoch) ||\n    lease.revocationEpoch < 0 ||\n    !Number.isSafeInteger(ackedSourceSeq) ||\n    ackedSourceSeq < 0\n  ) {\n    throw new Error(\"Warm run transition binding is invalid.\");\n  }\n  const { status: _status, result: _result, ...wire } = command;\n  const body = {\n    schema: \"paperclip.runner.warm-transition.v1\" as const,\n    oldIdentity: structuredClone(identity),\n    newIdentity: structuredClone(boundary.identity),\n    commandId: command.commandId,\n    controllerSeq: command.controllerSeq,\n    // Rust's closed Command representation serializes these optional fields.\n    commandFingerprint: canonicalDigest({\n      ...wire,\n      deadlineAt: null,\n      precondition: null,\n    }),\n    resultDigest: canonicalDigest(result),\n    oldAckedSourceSeq: ackedSourceSeq,\n    connection: structuredClone(boundary.connection),\n    runnerVersion,","sourceCodeStart":435,"sourceCodeEnd":471,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/packages/paperclip-runner/src/control-plane/durable-prp-control-plane.ts#L435-L471","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nreceipt({ lease: { revocationEpoch: null, expiresAtUnixMs: 0 }, ackedSourceSeq: '12' });\n// after\nreceipt({ lease: { revocationEpoch: 3, expiresAtUnixMs: Date.now() + 30_000 }, ackedSourceSeq: 12 });","handlingStrategy":"validation","validationCode":"function bindingValid(lease, ackedSourceSeq) {\n  return Number.isSafeInteger(lease?.expiresAtUnixMs) && lease.expiresAtUnixMs > 0 &&\n    Number.isSafeInteger(lease?.revocationEpoch) && lease.revocationEpoch >= 0 &&\n    Number.isSafeInteger(ackedSourceSeq) && ackedSourceSeq >= 0;\n}","typeGuard":"function isValidLease(l: unknown): l is { expiresAtUnixMs: number; revocationEpoch: number } {\n  const x = l as any;\n  return !!x && Number.isSafeInteger(x.expiresAtUnixMs) && x.expiresAtUnixMs > 0 &&\n    Number.isSafeInteger(x.revocationEpoch) && x.revocationEpoch >= 0;\n}","tryCatchPattern":"try {\n  const receipt = warmTransitionReceipt(binding);\n} catch (e) {\n  if (e.message.includes('transition binding is invalid')) {\n    const fresh = await acquireFreshLease();\n    return warmTransitionReceipt({ ...binding, lease: fresh });\n  }\n  throw e;\n}","preventionTips":["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."],"tags":["validation","invariant","state"],"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-14T05:17:10.506Z"}