paperclipai/paperclip · error · PhotonError
history_gap
history_gap
Error message
Photon recovery sequence exceeds the supported range
What it means
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.
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
Example fix
// before: silent narrowing risk is surfaced only at runtime
return Number(value);
// after: detect misparse early by validating the frame version before decoding
if (!isSupportedRecoveryFrameVersion(frame)) throw new Error("unsupported recovery frame version");
return Number(value); Defensive patterns
Strategy: try-catch
Type guard
function isSafeSequenceValue(v: bigint): boolean {
return v >= 0n && v <= BigInt(Number.MAX_SAFE_INTEGER);
} Try / catch
try {
const seq = photonEnvelopeSequence(frame);
} catch (e) {
if (e instanceof PhotonError && e.code === "history_gap" && /supported range/.test(e.message)) {
await resyncFromFrameBoundary(); // almost certainly a desynced/corrupt parse, not a real sequence
} else throw e;
} Prevention
- 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
When it happens
Trigger: photonEnvelopeSequence() reads the tag-8 field whose varint value > Number.MAX_SAFE_INTEGER; also reachable via length() when a length varint is absurdly large.
Common situations: 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.
Understand the failure class
Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.
Related errors
- invalid_response
- CreateOS process output has a sequence gap.
- CreateOS process sequence is invalid.
- delivery_unknown
- history_gap
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/f1ca19442adad329.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/photon/recovery-transport.ts:36
"/photon.imessage.v1.EventService/CatchUpEvents";
/** Pinned v2.1.0 protobuf envelope. The SDK decoder owns the event graph; this
* reader retains sequence-only/new-variant frames that its public API drops. */
export function photonEnvelopeSequence(bytes: Uint8Array): number | undefined {
let offset = 0;
const integer = () => {
let value = 0n;
for (let shift = 0n; shift < 70n; shift += 7n) {
if (offset >= bytes.length)
throw new PhotonError(
"invalid_response",
"Truncated Photon recovery frame",
);
const byte = bytes[offset++];
value |= BigInt(byte & 127) << shift;
if (!(byte & 128)) {
if (value > BigInt(Number.MAX_SAFE_INTEGER))
throw new PhotonError(
"history_gap",
"Photon recovery sequence exceeds the supported range",
);
return Number(value);
}
}
throw new PhotonError(
"invalid_response",
"Invalid Photon recovery integer",
);
};
let sequence: number | undefined;
while (offset < bytes.length) {
const tag = integer();
if (tag === 8) {
if (sequence !== undefined)
throw new PhotonError(
"invalid_response",View on GitHub (pinned to 3f1d897a7c)