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

Thrown in validatePushResponse when an ack's operation-tuple key (JSON of [id, kind, entity_rev, operation_sha256]) does not match ANY tuple the client actually sent. The hub is acknowledging an operation the client never pushed, or the tuple identity diverged between send and ack. This is the canonical 'extra or mismatched ack' invariant — every ack must correspond to a sent op.

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 d768ba3643)

Solutions

  1. Compare the acked tuple (id, kind, entity_rev, operation_sha256) against the pushed WireOp bodies — operationTupleKey JSON-encodes all four; any single field diff triggers this.
  2. If the hub normalizes entity_rev or operation_sha256 (e.g. leading zeros, hash casing), make the hub preserve the exact client-supplied values.
  3. Verify the hub is not mixing in acks from concurrent push batches or other devices.
  4. Upgrade hub and client together to a matching tuple-identity contract.

Example fix

// before: pushed op { id:'obs-1', kind:'observation', entity_rev:'1', operation_sha256:'abc...' }
//         but hub acks entity_rev:'01' (re-padded) -> tuple mismatch
// after:  hub returns entity_rev exactly as sent ('1') so operationTupleKey matches
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-check that every ack you EXPECT can match a sent tuple is impossible
// (acks come from the hub). Instead ensure your pushed ops produce stable keys:
import { createHash } from 'crypto';
function stableSha256(buf: string): string {
  return createHash('sha256').update(buf).digest('hex'); // identical on both sides
}

Try / catch

try { await cloudSync.push(pushed); }
catch (e) {
  if (e instanceof Error && e.message.includes('extra or mismatched acknowledgment tuple')) {
    // log the sent vs acked tuple sets, leave outbox intact for retry
    logger.error('SYNC', e.message, { sentCount: pushed.length }); return;
  }
  throw e;
}

Prevention

When it happens

Trigger: A 200 push response where at least one acked entry's (id, kind, entity_rev, operation_sha256) combination has no matching entry in the sentCounts multiset built from the pushed WireOp array. Reachable in validatePushResponse, which runs before any SQLite mutation in stampAcked.

Common situations: Hub rewrites operation_sha256 or entity_rev on its side (e.g. normalizing/padding), hub echoes back ops from a different device/batch, a proxy that recomputes hashes, or client/hub version skew where the tuple key components differ. The mismatch is almost always operation_sha256 or entity_rev differing.

Related errors


AI-assisted analysis of thedotmack/claude-mem@d768ba3643 (2026-08-12). Data as JSON: /api/errors/1a91cf808b2539ba. Report an issue: GitHub.