{"record":{"id":"acd43acf9b0ec060","repo":"thedotmack/claude-mem","slug":"sync-hub-push-checkpoint-order-requires-head-seq","errorCode":null,"errorMessage":"sync hub push: checkpoint order requires head_seq <= projected_seq","messagePattern":"sync hub push: checkpoint order requires head_seq <= projected_seq","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/services/sync/CloudSync.ts","lineNumber":1070,"sourceCode":"      }\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');\n      }\n    }\n  }\n\n  /**\n   * Stamp rows / delete outbox entries for a fully validated acknowledgment\n   * multiset. The hub may return entries in any order.\n   */\n  private stampAcked(acked: AckedOp[], pushed: WireOp[]): void {\n    const now = Date.now();\n    const bodies = new Map(pushed.map(op => {","sourceCodeStart":1052,"sourceCodeEnd":1088,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/e2d1df569a8f04075d40e92461128ece7cf04c82/src/services/sync/CloudSync.ts#L1052-L1088","documentation":"The push response reports head_seq greater than projected_seq. The hub's contract is to return 200 only once its authoritative projection checkpoint covers the commit, so head_seq <= projected_seq must always hold; head beyond the checkpoint means the hub answered before projection caught up, inverted the fields, or replicas disagree. Caught in validatePushResponse before stamping; flush() backs off and retries safely.","triggerScenarios":"Hub returns 200 after durable append but before the Pro projection advances and computes head_seq from a fresher replica than projected_seq; fields swapped in a serializer; a projection worker lagging under load while the response path doesn't wait for it.","commonSituations":"Hub performance change made projection async without preserving the wait-for-checkpoint guarantee; replica reads of the two fields from different nodes; post-incident hub where the checkpoint restore lagged.","solutions":["Poll GET /v1/sync/status during pushes and check whether head_seq/projected_seq genuinely invert or merely lag","On the hub, gate the 200 response on the projection checkpoint covering the appended seqs (head <= projected before answering)","If the fields were swapped by a refactor, fix the response construction and redeploy","Until fixed, the client safely retries — but throughput will collapse, so treat it as urgent hub-side"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Hub-side gate before answering 200 (mirror of the client invariant):\nfunction responseIsConsistent(headSeq: string, projectedSeq: string): boolean {\n  return BigInt(headSeq) <= BigInt(projectedSeq); // push contract: head <= projected\n}","typeGuard":"function pushCheckpointOrderOk(head_seq: string, projected_seq: string): boolean {\n  return BigInt(head_seq) <= BigInt(projected_seq);\n}","tryCatchPattern":"if (/head_seq <= projected_seq/.test(sync.status().lastError ?? '')) {\n  // hub answered before its projection covered the commit: fix the 200 gate; client retries are safe\n}","preventionTips":["Hub: block the response until the projection checkpoint covers the appended seqs","Derive head_seq and projected_seq from one consistent read of hub state","Load-test projection lag — if it can trail appends, the gate must still hold"],"tags":["sync","hub","integrity","sequence","checkpoint","invariant"],"backgroundTag":"sequence-invariant-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"}