thedotmack/claude-mem · error

sync hub push: acknowledgment seq exceeds head_seq

Error message

sync hub push: acknowledgment seq exceeds head_seq

What it means

An acked operation's seq is greater than the head_seq reported in the same response — the hub acknowledged a log position beyond the durable head it itself declares. Internal inconsistency; caught before stampAcked, so nothing changes locally and flush() retries with backoff.

Solutions

  1. Reproduce with a single-op push and compare ack.seq against reported head_seq
  2. On the hub, derive head_seq and ack seqs from one consistent point (same transaction/read) when assembling the response
  3. Ensure reads for the response come from the authoritative log, not a lagging replica
  4. Redeploy the fixed hub; clients recover automatically on the next retry
Defensive patterns

Strategy: validation

Validate before calling

// Hub-side self-check: no ack may outrun the reported head:
const maxAckSeq = acked.reduce((m, a) => (BigInt(a.seq) > BigInt(m) ? a.seq : m), '0');
if (BigInt(maxAckSeq) > BigInt(headSeq)) throw new Error('ack seq beyond head — refuse to respond');

Type guard

function acksWithinHead(acked: { seq: string }[], head_seq: string): boolean {
  return acked.every(a => BigInt(a.seq) <= BigInt(head_seq));
}

Try / catch

if (/acknowledgment seq exceeds head_seq/.test(sync.status().lastError ?? '')) {
  // inconsistent response assembly on the hub: compute head and acks from one transaction/read
}

Prevention

When it happens

Trigger: head_seq computed from a stale replica while acks carry fresh seqs; a hub bug computing head_seq before the append transaction commits; log truncation/rollback between computing head and emitting acks.

Common situations: Hub scaled to multi-replica reads without read-your-writes; a hub refactor reordered head computation vs append; snapshot isolation issues in the hub's response assembly.

Related errors


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

Appendix: source

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

    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) {
      throw new Error('sync hub push: checkpoint order requires head_seq <= projected_seq');
    }
    for (const ack of response.acked) {
      if (compareCanonicalDecimals(ack.seq, response.head_seq) > 0) {
        throw new Error('sync hub push: acknowledgment seq exceeds head_seq');
      }
      if (compareCanonicalDecimals(ack.seq, response.projected_seq) > 0) {
        throw new Error('sync hub push: sent operation is not covered by projected_seq');
      }
    }
  }

  /**
   * Stamp rows / delete outbox entries for a fully validated acknowledgment
   * multiset. The hub may return entries in any order.
   */
  private stampAcked(acked: AckedOp[], pushed: WireOp[]): void {
    const now = Date.now();
    const bodies = new Map(pushed.map(op => {
      const body = parseCanonicalOperation(op);
      return [operationTupleKey({
        id: body.id,
        kind: body.kind,

View on GitHub (pinned to e2d1df569a)