{"record":{"id":"20a4120e8b007ac6","repo":"thedotmack/claude-mem","slug":"syncapply-sequence-gap-expected-incrementcanon","errorCode":null,"errorMessage":"SyncApply: sequence gap (expected ${incrementCanonicalDecimal(lastSeq)}, got ${seq})","messagePattern":"SyncApply: sequence gap \\(expected (.+?), got (.+?)\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/services/sync/SyncApply.ts","lineNumber":462,"sourceCode":"      epochReset: false,\n    };\n    if (ops.length === 0) return result;\n\n    const chromaJobs: ChromaJob[] = [];\n\n    const tx = this.db.transaction(() => {\n      const cursor = this.getCursor();\n      let lastSeq = cursor;\n\n      for (const op of ops) {\n        const seq = assertCanonicalDecimal(op.seq, { positive: true });\n        assertCanonicalDecimal(op.rev, { positive: true });\n        // Strict HTTP pages describe the exact raw suffix after our cursor.\n        // Validate every supplied sequence before the ordinary replay skip;\n        // otherwise a stale prefix (even an out-of-order one) is silently\n        // discarded and a malformed page can look contiguous.\n        if (options.requireContiguous === true && seq !== incrementCanonicalDecimal(lastSeq)) {\n          throw new Error(`SyncApply: sequence gap (expected ${incrementCanonicalDecimal(lastSeq)}, got ${seq})`);\n        }\n        if (compareCanonicalDecimals(seq, cursor) <= 0) {\n          result.skippedCursor++;\n          continue;\n        }\n        if (compareCanonicalDecimals(seq, lastSeq) <= 0) {\n          throw new Error(`SyncApply: ops out of order (seq ${op.seq} after ${lastSeq})`);\n        }\n        lastSeq = seq;\n\n        if (op.origin_device === this.deviceId) {\n          result.skippedOwn++;\n          continue;\n        }\n\n        let outcome: 'applied' | 'stale';\n        if (op.kind === 'mutation') {\n          outcome = this.applyMutation(op);","sourceCodeStart":444,"sourceCodeEnd":480,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d768ba364302d12b76e69e4f021f0bb1d2d50ed6/src/services/sync/SyncApply.ts#L444-L480","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: apply.applyOps(page, { requireContiguous: true }) // throws on any gap\n// after:  apply.applyOps(page, { requireContiguous: false }) // skip already-applied via cursor","handlingStrategy":"validation","validationCode":"// Before applyOps, decide whether strict contiguity is required by the page source:\nfunction shouldRequireContiguous(pageSource: 'strict-http' | 'backfill'): boolean {\n  return pageSource === 'strict-http';\n}\n// If gaps are possible, pass requireContiguous: false and rely on cursor skipping.\nconst opts = { requireContiguous: shouldRequireContiguous(source) };","typeGuard":null,"tryCatchPattern":"try { apply.applyOps(page, { requireContiguous: true }); }\ncatch (e) {\n  if (e instanceof Error && e.message.startsWith('SyncApply: sequence gap')) {\n    // re-fetch from current cursor; strict page was incomplete\n    page = await refetchFromCursor(apply.getCursor());\n    apply.applyOps(page, { requireContiguous: true });\n    return;\n  }\n  throw e;\n}","preventionTips":["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."],"tags":["sync","sync-apply","validation","sequence-integrity","pagination"],"backgroundTag":null,"analyzedSha":"d768ba364302d12b76e69e4f021f0bb1d2d50ed6","analyzedAt":"2026-08-12T23:52:55.241Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}