{"record":{"id":"f1ca19442adad329","repo":"paperclipai/paperclip","slug":"history-gap-photon-recovery-sequence-exceeds-the-supported","errorCode":"history_gap","errorMessage":"Photon recovery sequence exceeds the supported range","messagePattern":"Photon recovery sequence exceeds the supported range","errorType":"error_code","errorClass":"PhotonError","httpStatus":null,"severity":"error","filePath":"server/src/services/photon/recovery-transport.ts","lineNumber":36,"sourceCode":"  \"/photon.imessage.v1.EventService/CatchUpEvents\";\n\n/** Pinned v2.1.0 protobuf envelope. The SDK decoder owns the event graph; this\n * reader retains sequence-only/new-variant frames that its public API drops. */\nexport function photonEnvelopeSequence(bytes: Uint8Array): number | undefined {\n  let offset = 0;\n  const integer = () => {\n    let value = 0n;\n    for (let shift = 0n; shift < 70n; shift += 7n) {\n      if (offset >= bytes.length)\n        throw new PhotonError(\n          \"invalid_response\",\n          \"Truncated Photon recovery frame\",\n        );\n      const byte = bytes[offset++];\n      value |= BigInt(byte & 127) << shift;\n      if (!(byte & 128)) {\n        if (value > BigInt(Number.MAX_SAFE_INTEGER))\n          throw new PhotonError(\n            \"history_gap\",\n            \"Photon recovery sequence exceeds the supported range\",\n          );\n        return Number(value);\n      }\n    }\n    throw new PhotonError(\n      \"invalid_response\",\n      \"Invalid Photon recovery integer\",\n    );\n  };\n  let sequence: number | undefined;\n  while (offset < bytes.length) {\n    const tag = integer();\n    if (tag === 8) {\n      if (sequence !== undefined)\n        throw new PhotonError(\n          \"invalid_response\",","sourceCodeStart":18,"sourceCodeEnd":54,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/server/src/services/photon/recovery-transport.ts#L18-L54","documentation":"Recovery sequence numbers are decoded as big integers then narrowed to JavaScript numbers. If the varint decodes to a value greater than Number.MAX_SAFE_INTEGER (2^53 - 1), the sequence cannot be represented losslessly and the decoder throws history_gap — the stream's sequence space has exceeded what the receiver supports, so a gap-classified abort is safer than silently losing precision.","triggerScenarios":"photonEnvelopeSequence() reads the tag-8 field whose varint value > Number.MAX_SAFE_INTEGER; also reachable via length() when a length varint is absurdly large.","commonSituations":"Corrupted or hostile frame bytes decoded as a giant varint (missing terminator misaligning fields); a Photon server with a wildly divergent/wrong sequence counter; protocol desync where a non-sequence varint is parsed as the sequence field after a format change.","solutions":["Treat it as corruption first: discard the frame and reconnect/resync; a legit sequence this large usually indicates a desynced parse, not a real value","Verify field alignment: confirm the frame format/version matches what photonEnvelopeSequence() expects (tag 8 = sequence)","Check the Photon server's sequence counter for a runaway/overflow bug and fix or reset it","If genuinely huge sequences are expected, migrate the decoder to return BigInt end-to-end instead of narrowing to Number"],"exampleFix":"// before: silent narrowing risk is surfaced only at runtime\nreturn Number(value);\n// after: detect misparse early by validating the frame version before decoding\nif (!isSupportedRecoveryFrameVersion(frame)) throw new Error(\"unsupported recovery frame version\");\nreturn Number(value);","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"function isSafeSequenceValue(v: bigint): boolean {\n  return v >= 0n && v <= BigInt(Number.MAX_SAFE_INTEGER);\n}","tryCatchPattern":"try {\n  const seq = photonEnvelopeSequence(frame);\n} catch (e) {\n  if (e instanceof PhotonError && e.code === \"history_gap\" && /supported range/.test(e.message)) {\n    await resyncFromFrameBoundary(); // almost certainly a desynced/corrupt parse, not a real sequence\n  } else throw e;\n}","preventionTips":["Treat huge decoded values as corruption signals and resync rather than trusting them","Verify field alignment when frame format versions change","Monitor Photon's sequence counter for runaway growth","If sequences could legitimately exceed 2^53, migrate the pipeline to BigInt"],"tags":["photon","varint","overflow","sequence"],"backgroundTag":"value-out-of-range","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}