thedotmack/claude-mem · error
sync hub push: 200 response contains an extra or mismatched…
Error message
sync hub push: 200 response contains an extra or mismatched acknowledgment tuple
What it means
validatePushResponse() builds the exact multiset of sent (id, kind, entity_rev, operation_sha256) tuples and rejects any acked tuple that was not in the request. The hub must acknowledge exactly what was pushed — an extra or unknown tuple means the response belongs to a different request or was fabricated. The check runs before any SQLite mutation (stampAcked), so local state is safe; flush() backs off and retries, and hub dedupe makes re-pushes harmless. Treat persistent occurrences as a hub integrity or routing problem.
Solutions
- Log the pushed tuple keys next to the acked ones to identify what the hub returned that was never sent
- Verify all hub replicas share one authoritative log — no per-replica append paths behind a load balancer
- Disable POST caching at every intermediary in front of the hub
- Upgrade/patch the hub if its ack derivation diverges from the appended ops
- Last resort: the local queue can be re-drained from scratch for the affected entities; hub dedupe on (origin_device, kind, origin_id, rev) makes the re-push safe
Defensive patterns
Strategy: try-catch
Validate before calling
// You cannot pre-validate a remote hub's behavior; pre-compute your sent tuple set
// so you can diff against the hub response when investigating:
const sentKeys = new Set(ops.map(op => {
const body = parseCanonicalOperation(op);
return JSON.stringify([body.id, body.kind, body.entity_rev, op.operation_sha256]);
}));
// on failure, diff response.acked keys against sentKeys to see the phantom tuple Try / catch
if (/extra or mismatched acknowledgment tuple/.test(sync.status().lastError ?? '')) {
// hub integrity/routing alert: verify single durable log behind the LB,
// disable POST caching at intermediaries, then let the backoff retry
} Prevention
- Run the hub as one authoritative log (or a single-writer sequencer) behind any load balancer
- Disable caching of POST at every CDN/proxy in front of the hub
- Alert on this message — it should be impossible in a healthy deployment
When it happens
Trigger: Hub acks ops from a previous batch (cross-request state bleed); load-balanced hub replicas with divergent logs returning each other's acknowledgments; a proxy cache replaying an old 200 body; a hub bug echoing expected acks derived from something other than the appended ops.
Common situations: Sticky-session-less LB over hub replicas that don't share a single durable log; an intermediary caching POST responses (RFC violation but it happens); hub mid-incident returning stale responses.
Related errors
- sync hub push: 200 response acknowledgment multiplicity…
- sync hub push: acknowledgment seq exceeds head_seq
- sync hub push: duplicate operation tuple claimed different…
- sync hub push: checkpoint order requires head_seq <=…
- sync hub push: distinct operation tuples claimed the same…
AI-assisted analysis of thedotmack/claude-mem@e2d1df569a (2026-08-20).
Data as JSON: /api/errors/1a91cf808b2539ba.
Report an issue: GitHub.
Appendix: source
Thrown at src/services/sync/CloudSync.ts:1039
const sentCounts = new Map<string, number>();
for (const op of pushed) {
const body = parseCanonicalOperation(op);
const key = operationTupleKey({
id: body.id,
kind: body.kind,
entity_rev: body.entity_rev,
operation_sha256: op.operation_sha256,
});
sentCounts.set(key, (sentCounts.get(key) ?? 0) + 1);
}
const ackCounts = new Map<string, number>();
const tupleSeq = new Map<string, string>();
const seqTuple = new Map<string, string>();
for (const ack of response.acked) {
const key = operationTupleKey(ack);
if (!sentCounts.has(key)) {
throw new Error('sync hub push: 200 response contains an extra or mismatched acknowledgment tuple');
}
ackCounts.set(key, (ackCounts.get(key) ?? 0) + 1);
const priorTupleSeq = tupleSeq.get(key);
if (priorTupleSeq !== undefined && priorTupleSeq !== ack.seq) {
throw new Error('sync hub push: duplicate operation tuple claimed different sequences');
}
tupleSeq.set(key, ack.seq);
const priorSeqTuple = seqTuple.get(ack.seq);
if (priorSeqTuple !== undefined && priorSeqTuple !== key) {
throw new Error('sync hub push: distinct operation tuples claimed the same sequence');
}
seqTuple.set(ack.seq, key);
}
for (const [key, expected] of sentCounts) {
const actual = ackCounts.get(key) ?? 0;View on GitHub (pinned to e2d1df569a)