{"record":{"id":"9b68d43f0987777c","repo":"thedotmack/claude-mem","slug":"syncapply-ops-out-of-order-seq-op-seq-after","errorCode":null,"errorMessage":"SyncApply: ops out of order (seq ${op.seq} after ${lastSeq})","messagePattern":"SyncApply: ops out of order \\(seq (.+?) after (.+?)\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/services/sync/SyncApply.ts","lineNumber":469,"sourceCode":"      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);\n        } else {\n          outcome = this.applyCanonicalRowOp(op, chromaJobs);\n        }\n        if (outcome === 'applied') result.applied++;\n        else result.skippedStale++;\n      }\n","sourceCodeStart":451,"sourceCodeEnd":487,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/e2d1df569a8f04075d40e92461128ece7cf04c82/src/services/sync/SyncApply.ts#L451-L487","documentation":"Thrown by SyncApply.applyOps when, after the cursor skip (compareCanonicalDecimals(seq, cursor) <= 0), an op's seq is still <= lastSeq — i.e. the page contains a duplicate or regressed sequence relative to ops already accepted in this batch. It is the in-batch ordering guard; with requireContiguous enabled the gap check at SyncApply.ts:462 fires first, so this error is effectively the non-strict (socket push) path's protection against at-least-once delivery duplicating an op inside one page.","triggerScenarios":"applyOps receiving a page containing the same seq twice ([10,10]) or a regression ([10,11,10]) with any op above the cursor — typically a server double-emitting an op, a delivery layer stitching overlapping retry pages, or a producer bug re-sending the tail of the previous batch.","commonSituations":"Message queue redelivery duplicating an op within one delivered page; server retry logic appending the previous page's last op to the next page; a server-side migration replaying ops without dedupe.","solutions":["Deduplicate ops by seq (keep first occurrence) before calling applyOps.","Re-pull the page from the cursor instead of replaying a possibly-corrupted push payload.","If duplicates persist, capture the offending page and report a server-side emit/dedupe bug — the hub is violating the strictly-increasing seq contract."],"exampleFix":"// before\nconst result = syncApply.applyOps(ops, { epoch });\n\n// after\nconst seen = new Set<string>();\nconst deduped = ops.filter(op => {\n  if (seen.has(op.seq)) return false;\n  seen.add(op.seq);\n  return true;\n});\nconst result = syncApply.applyOps(deduped, { epoch });","handlingStrategy":"validation","validationCode":"// Deduplicate and order-check a page before non-strict apply.\nfunction sanitizePage(ops: Array<{ seq: string }>): Array<{ seq: string }> {\n  const bySeq = new Map(ops.map(op => [op.seq, op]));\n  return [...bySeq.values()].sort((a, b) => compareCanonicalDecimals(a.seq, b.seq));\n}","typeGuard":null,"tryCatchPattern":"try {\n  result = syncApply.applyOps(ops, { epoch });\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith('SyncApply: ops out of order')) {\n    const sanitized = sanitizePage(ops);\n    result = syncApply.applyOps(sanitized, { epoch });\n  } else throw e;\n}","preventionTips":["Dedupe by seq at the transport boundary if your delivery is at-least-once.","Sort pages by seq before apply when the producer does not guarantee ordering.","Treat repeated occurrences as a producer bug to report, not a client condition to permanently mask."],"tags":["sync","data-integrity","duplicate-detection","ordering"],"backgroundTag":"out-of-order-sequence","analyzedSha":"e2d1df569a8f04075d40e92461128ece7cf04c82","analyzedAt":"2026-08-20T23:58:13.836Z","contentChangedAt":"2026-08-20T23:58:13.836Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}