thedotmack/claude-mem · error

cloud sync ack conflict for

Error message

cloud sync ack conflict for ${body.id} rev ${body.entity_rev}

What it means

During stampAcked(), advanceEntityHead() found sync_entity_heads already holding the same entity_id at the same entity_rev but with a different operation_sha256. Canonical bytes are content-derived, so (id, rev) must always hash identically — this throw means the bytes the hub just acknowledged diverge from the local head record for the identical revision. The stamping transaction aborts entirely (no partial writes), flush() records lastError, and because the conflict is deterministic the lane stalls on that batch until resolved. Typical roots: canonical serialization or payload_schema_version drift between client versions, a cloned/restored database, or two machines sharing one device id.

Solutions

  1. Identify the entity: SELECT entity_rev, operation_sha256 FROM sync_entity_heads WHERE entity_id = '<id from message>' and compare with the just-pushed op's hash
  2. If the local head is stale (abandoned branch / restored backup), delete that head row — it re-derives from the next ack
  3. If the pushed bytes are the divergent side, bump the revision: touch the entity so a higher-rev snapshot is queued (the order>0 path supersedes the conflict)
  4. Check for duplicated device ids across machines (both derive identical entity ids) — regenerate deviceId on the clone
  5. If it recurs on a single device with consistent versions, capture both bodies and report as a canonicalization bug

Example fix

-- before: head row pins rev 5 to a divergent hash, ack for rev 5 conflicts
-- entity_id='memory:abc' entity_rev='5' operation_sha256='oldhash'

-- after: drop the stale head so it re-derives from the next acknowledged op
DELETE FROM sync_entity_heads WHERE entity_id = 'memory:abc' AND entity_rev = '5';
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-flush detector: same entity_id + same rev with divergent hashes across local tables:
const conflicts = db.prepare(`
  SELECT h.entity_id, h.entity_rev
  FROM sync_entity_heads h
  JOIN sync_content_outbox o
    ON o.entity_id = h.entity_id AND o.entity_rev = h.entity_rev
  WHERE o.operation_sha256 != h.operation_sha256
`).all();
// resolve each (drop stale head or bump the rev) before it stalls a flush

Try / catch

// Thrown inside stampAcked's transaction, captured into lastError — poll:
const m = (sync.status().lastError ?? '').match(/cloud sync ack conflict for (\S+) rev (\d+)/);
if (m) {
  const [, entityId, rev] = m;
  // inspect sync_entity_heads for (entityId): drop the stale row or touch the
  // entity to queue a higher-rev snapshot, then let the retry proceed
}

Prevention

When it happens

Trigger: Another device (or an older app version with different canonical serialization) produced different bytes for the same entity at the same rev and the hub acked them; the data directory was cloned to a second machine mid-sync so both derive the same entity ids; sync_entity_heads was restored from a backup taken on a divergent branch; hand-edited sync tables.

Common situations: Copying the app data folder between machines without resetting device identity; upgrading one device to a build that changed payload serialization while another device holds the head at the same rev; restoring an old DB backup over newer sync state.

Related errors


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

Appendix: source

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

        this.reconcileAckedContent(body, pushedOp.operationSha256, now);
      }
    });
    tx();
  }

  private advanceEntityHead(
    body: ReturnType<typeof parseCanonicalOperation>,
    operationSha256: string,
    now: number,
  ): void {
    const current = this.db.prepare(`
      SELECT entity_rev, operation_sha256 FROM sync_entity_heads WHERE entity_id = ?
    `).get(body.id) as { entity_rev: string; operation_sha256: string } | undefined;
    if (current) {
      const order = compareCanonicalDecimals(body.entity_rev, current.entity_rev);
      if (order < 0) return;
      if (order === 0 && current.operation_sha256 !== operationSha256) {
        throw new Error(`cloud sync ack conflict for ${body.id} rev ${body.entity_rev}`);
      }
    }
    this.db.prepare(`
      INSERT INTO sync_entity_heads
        (entity_id, kind, origin_device_id, origin_local_id, entity_rev,
         operation_sha256, deleted, updated_at_epoch)
      VALUES (?, ?, ?, ?, ?, ?, ?, ?)
      ON CONFLICT(entity_id) DO UPDATE SET
        entity_rev=excluded.entity_rev,
        operation_sha256=excluded.operation_sha256,
        deleted=excluded.deleted,
        updated_at_epoch=excluded.updated_at_epoch
    `).run(
      body.id, body.kind, body.origin_device_id, body.origin_local_id,
      body.entity_rev, operationSha256, body.deleted ? 1 : 0, now,
    );
  }

View on GitHub (pinned to e2d1df569a)