thedotmack/claude-mem · error
SyncApply: sequence gap (expected ${incrementCanonicalDecima
Error message
SyncApply: sequence gap (expected ${incrementCanonicalDecimal(lastSeq)}, got ${seq}) What it means
Thrown by SyncApply when applying a page of ops in strict-contiguous mode (options.requireContiguous === true) and an op's seq is not exactly lastSeq + 1. Strict HTTP pages must describe the precise suffix after the cursor with no gaps; a gap means the page is incomplete or reordered, which would silently drop ops if not enforced.
Source
Thrown at src/services/sync/SyncApply.ts:462
epochReset: false,
};
if (ops.length === 0) return result;
const chromaJobs: ChromaJob[] = [];
const tx = this.db.transaction(() => {
const cursor = this.getCursor();
let lastSeq = cursor;
for (const op of ops) {
const seq = assertCanonicalDecimal(op.seq, { positive: true });
assertCanonicalDecimal(op.rev, { positive: true });
// Strict HTTP pages describe the exact raw suffix after our cursor.
// Validate every supplied sequence before the ordinary replay skip;
// otherwise a stale prefix (even an out-of-order one) is silently
// discarded and a malformed page can look contiguous.
if (options.requireContiguous === true && seq !== incrementCanonicalDecimal(lastSeq)) {
throw new Error(`SyncApply: sequence gap (expected ${incrementCanonicalDecimal(lastSeq)}, got ${seq})`);
}
if (compareCanonicalDecimals(seq, cursor) <= 0) {
result.skippedCursor++;
continue;
}
if (compareCanonicalDecimals(seq, lastSeq) <= 0) {
throw new Error(`SyncApply: ops out of order (seq ${op.seq} after ${lastSeq})`);
}
lastSeq = seq;
if (op.origin_device === this.deviceId) {
result.skippedOwn++;
continue;
}
let outcome: 'applied' | 'stale';
if (op.kind === 'mutation') {
outcome = this.applyMutation(op);View on GitHub (pinned to d768ba3643)
Solutions
- If the page genuinely has a gap, re-fetch from the current cursor (the hub must return a contiguous suffix).
- If you do not need strict contiguity, call applyOps without requireContiguous (or set it false) to allow cursor-based skipping.
- Verify the cursor used to request the page matches the local getCursor() at apply time — a moved cursor produces apparent gaps.
- Check hub pagination for an off-by-one that drops the first/last op of a page.
Example fix
// before: apply.applyOps(page, { requireContiguous: true }) // throws on any gap
// after: apply.applyOps(page, { requireContiguous: false }) // skip already-applied via cursor Defensive patterns
Strategy: validation
Validate before calling
// Before applyOps, decide whether strict contiguity is required by the page source:
function shouldRequireContiguous(pageSource: 'strict-http' | 'backfill'): boolean {
return pageSource === 'strict-http';
}
// If gaps are possible, pass requireContiguous: false and rely on cursor skipping.
const opts = { requireContiguous: shouldRequireContiguous(source) }; Try / catch
try { apply.applyOps(page, { requireContiguous: true }); }
catch (e) {
if (e instanceof Error && e.message.startsWith('SyncApply: sequence gap')) {
// re-fetch from current cursor; strict page was incomplete
page = await refetchFromCursor(apply.getCursor());
apply.applyOps(page, { requireContiguous: true });
return;
}
throw e;
} Prevention
- Only set requireContiguous for pages whose source guarantees a contiguous suffix.
- Re-request the page from the live cursor when a gap appears rather than advancing.
- Keep the hub's first/last page boundary logic covered by pagination tests.
When it happens
Trigger: In the applyOps transaction, for each op: assertCanonicalDecimal(op.seq), then if requireContiguous and seq !== incrementCanonicalDecimal(lastSeq), throw. lastSeq starts at the cursor. Even a stale-prefix op is checked before the cursor-skip, so an out-of-order stale prefix also trips it.
Common situations: Caller passed requireContiguous:true for a strict HTTP page that had a missing seq (hub omitted an op), a page fetched from a cursor that moved between request and response, or non-contiguous data fed into a strict-merge path. Indicates the page boundary contract was violated.
Related errors
- SyncApply: ops out of order (seq ${op.seq} after ${lastSeq})
- sync hub push: duplicate operation tuple claimed different s
- sync hub push: distinct operation tuples claimed the same se
- sync hub push: acknowledgment seq exceeds head_seq
- sync hub push: sent operation is not covered by projected_se
AI-assisted analysis of thedotmack/claude-mem@d768ba3643 (2026-08-12).
Data as JSON: /api/errors/20a4120e8b007ac6.
Report an issue: GitHub.