{"record":{"id":"f93e0925e96b98df","repo":"thedotmack/claude-mem","slug":"sync-hub-push-200-response-acknowledgment-multipl","errorCode":null,"errorMessage":"sync hub push: 200 response acknowledgment multiplicity mismatch (expected ${expected}, received ${actual})","messagePattern":"sync hub push: 200 response acknowledgment multiplicity mismatch \\(expected (.+?), received (.+?)\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/services/sync/CloudSync.ts","lineNumber":1059,"sourceCode":"      ackCounts.set(key, (ackCounts.get(key) ?? 0) + 1);\n\n      const priorTupleSeq = tupleSeq.get(key);\n      if (priorTupleSeq !== undefined && priorTupleSeq !== ack.seq) {\n        throw new Error('sync hub push: duplicate operation tuple claimed different sequences');\n      }\n      tupleSeq.set(key, ack.seq);\n\n      const priorSeqTuple = seqTuple.get(ack.seq);\n      if (priorSeqTuple !== undefined && priorSeqTuple !== key) {\n        throw new Error('sync hub push: distinct operation tuples claimed the same sequence');\n      }\n      seqTuple.set(ack.seq, key);\n    }\n\n    for (const [key, expected] of sentCounts) {\n      const actual = ackCounts.get(key) ?? 0;\n      if (actual !== expected) {\n        throw new Error(\n          `sync hub push: 200 response acknowledgment multiplicity mismatch (expected ${expected}, received ${actual})`\n        );\n      }\n    }\n    if (ackCounts.size !== sentCounts.size) {\n      // Defensive: the unknown-tuple branch above should make this impossible.\n      throw new Error('sync hub push: 200 response acknowledgment multiset mismatch');\n    }\n\n    if (compareCanonicalDecimals(response.head_seq, response.projected_seq) > 0) {\n      throw new Error('sync hub push: checkpoint order requires head_seq <= projected_seq');\n    }\n    for (const ack of response.acked) {\n      if (compareCanonicalDecimals(ack.seq, response.head_seq) > 0) {\n        throw new Error('sync hub push: acknowledgment seq exceeds head_seq');\n      }\n      if (compareCanonicalDecimals(ack.seq, response.projected_seq) > 0) {\n        throw new Error('sync hub push: sent operation is not covered by projected_seq');","sourceCodeStart":1041,"sourceCodeEnd":1077,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/e2d1df569a8f04075d40e92461128ece7cf04c82/src/services/sync/CloudSync.ts#L1041-L1077","documentation":"Per-tuple acknowledgment counts don't match what was sent: a tuple pushed N times was acked M≠N times. Duplicates in one push are legitimate (the same entity/rev op can appear in more than one lane), which is why counts, not just presence, are checked. Caught before stamping; flush() retries with backoff. Usually means the hub collapsed duplicate submissions into one ack while the client sent copies, or acked one copy too many.","triggerScenarios":"The same op tuple queued twice locally (e.g. content lane plus a frozen re-enqueue) and the hub's dedupe acks it once; hub coalesces duplicate appends into one ack entry but the client counts two; a hub contract change on duplicate handling.","commonSituations":"Version skew where the hub changed from per-copy acks to per-unique-op acks; requeued retries producing duplicate tuples in a single batch; migration paths that legitimately double-enqueue.","solutions":["Log sentCounts vs ackCounts around the failing push to see which tuple's multiplicity differs","Align hub behavior with the contract: every pushed copy gets one ack entry, including duplicates","Or, if the hub intentionally coalesces, dedupe identical tuples client-side before packing the batch","Check whether a re-enqueue path can queue the exact same (entity_id, entity_rev) twice in one drain and prevent it"],"exampleFix":"// before: same tuple can enter the batch twice\nbuf.push(op); buf.push(op);\n\n// after: dedupe by tuple key when packing\nconst key = operationTupleKey({ id, kind, entity_rev, operation_sha256 });\nif (!seen.has(key)) { seen.add(key); buf.push(op); }","handlingStrategy":"try-catch","validationCode":"// If you assemble batches yourself, dedupe identical tuples before packing:\nconst seen = new Set<string>();\nconst packed = ops.filter(op => {\n  const body = parseCanonicalOperation(op);\n  const key = JSON.stringify([body.id, body.kind, body.entity_rev, op.operation_sha256]);\n  if (seen.has(key)) return false;\n  seen.add(key);\n  return true;\n});","typeGuard":null,"tryCatchPattern":"const m = (sync.status().lastError ?? '').match(/multiplicity mismatch \\(expected (\\d+), received (\\d+)\\)/);\nif (m) {\n  // expected>received: hub coalesced duplicate copies — align on per-copy acks or dedupe client-side\n}","preventionTips":["Agree on duplicate-op ack semantics (per sent copy) between client and hub, in the contract tests","Avoid enqueue paths that can queue the identical (entity_id, entity_rev) tuple twice in one drain","Cover duplicate-tuple pushes in hub fixtures"],"tags":["sync","hub","integrity","ack","multiplicity","invariant"],"backgroundTag":"data-integrity-violation","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"}