thedotmack/claude-mem · error

sync hub push: distinct operation tuples claimed the same…

Error message

sync hub push: distinct operation tuples claimed the same sequence

What it means

Two distinct acked tuples claim the same seq — the hub handed one log sequence number to two different operations. Sequence ownership must be unique (one seq ↔ one op); this check, together with the tuple→seq check, proves the bijective mapping before anything is stamped. Caught pre-stamp in validatePushResponse; flush() backs off and retries. Indicates a hub sequence-allocation bug or replica divergence.

Solutions

  1. Inspect the hub log around the reported seq for double-assigned entries
  2. Make sequence allocation single-writer/atomic (transactional autoincrement on the authoritative log)
  3. If replicas append in parallel, route all appends through one sequencer before fan-out
  4. Repair or re-sequence the affected log segment, then let clients re-converge via retry
Defensive patterns

Strategy: try-catch

Try / catch

if (/distinct operation tuples claimed the same sequence/.test(sync.status().lastError ?? '')) {
  // seq double-allocation on the hub: inspect the log around that seq, fix the sequencer
}

Prevention

When it happens

Trigger: Hub allocates seqs with a non-atomic counter under concurrent appends; two replicas independently assigned the same seq after a partition; a rebuild/replay of the hub log reused sequence numbers.

Common situations: Hub concurrency regression after a performance refactor; multi-writer hub topology introduced without single-writer sequencing; log replay tooling reusing seqs.

Related errors


AI-assisted analysis of thedotmack/claude-mem@e2d1df569a (2026-08-20). Data as JSON: /api/errors/d9657f0c6c43a206. Report an issue: GitHub.

Appendix: source

Thrown at src/services/sync/CloudSync.ts:1051

    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;
      if (actual !== expected) {
        throw new Error(
          `sync hub push: 200 response acknowledgment multiplicity mismatch (expected ${expected}, received ${actual})`
        );
      }
    }
    if (ackCounts.size !== sentCounts.size) {
      // Defensive: the unknown-tuple branch above should make this impossible.
      throw new Error('sync hub push: 200 response acknowledgment multiset mismatch');
    }

    if (compareCanonicalDecimals(response.head_seq, response.projected_seq) > 0) {

View on GitHub (pinned to e2d1df569a)