{"record":{"id":"ecd59123cbb04776","repo":"thedotmack/claude-mem","slug":"cloud-sync-ack-conflict-for-body-id-rev-body","errorCode":null,"errorMessage":"cloud sync ack conflict for ${body.id} rev ${body.entity_rev}","messagePattern":"cloud sync ack conflict for (.+?) rev (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/services/sync/CloudSync.ts","lineNumber":1137,"sourceCode":"        this.reconcileAckedContent(body, pushedOp.operationSha256, now);\n      }\n    });\n    tx();\n  }\n\n  private advanceEntityHead(\n    body: ReturnType<typeof parseCanonicalOperation>,\n    operationSha256: string,\n    now: number,\n  ): void {\n    const current = this.db.prepare(`\n      SELECT entity_rev, operation_sha256 FROM sync_entity_heads WHERE entity_id = ?\n    `).get(body.id) as { entity_rev: string; operation_sha256: string } | undefined;\n    if (current) {\n      const order = compareCanonicalDecimals(body.entity_rev, current.entity_rev);\n      if (order < 0) return;\n      if (order === 0 && current.operation_sha256 !== operationSha256) {\n        throw new Error(`cloud sync ack conflict for ${body.id} rev ${body.entity_rev}`);\n      }\n    }\n    this.db.prepare(`\n      INSERT INTO sync_entity_heads\n        (entity_id, kind, origin_device_id, origin_local_id, entity_rev,\n         operation_sha256, deleted, updated_at_epoch)\n      VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n      ON CONFLICT(entity_id) DO UPDATE SET\n        entity_rev=excluded.entity_rev,\n        operation_sha256=excluded.operation_sha256,\n        deleted=excluded.deleted,\n        updated_at_epoch=excluded.updated_at_epoch\n    `).run(\n      body.id, body.kind, body.origin_device_id, body.origin_local_id,\n      body.entity_rev, operationSha256, body.deleted ? 1 : 0, now,\n    );\n  }\n","sourceCodeStart":1119,"sourceCodeEnd":1155,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/e2d1df569a8f04075d40e92461128ece7cf04c82/src/services/sync/CloudSync.ts#L1119-L1155","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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","If the local head is stale (abandoned branch / restored backup), delete that head row — it re-derives from the next ack","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)","Check for duplicated device ids across machines (both derive identical entity ids) — regenerate deviceId on the clone","If it recurs on a single device with consistent versions, capture both bodies and report as a canonicalization bug"],"exampleFix":"-- before: head row pins rev 5 to a divergent hash, ack for rev 5 conflicts\n-- entity_id='memory:abc' entity_rev='5' operation_sha256='oldhash'\n\n-- after: drop the stale head so it re-derives from the next acknowledged op\nDELETE FROM sync_entity_heads WHERE entity_id = 'memory:abc' AND entity_rev = '5';","handlingStrategy":"try-catch","validationCode":"// Pre-flush detector: same entity_id + same rev with divergent hashes across local tables:\nconst conflicts = db.prepare(`\n  SELECT h.entity_id, h.entity_rev\n  FROM sync_entity_heads h\n  JOIN sync_content_outbox o\n    ON o.entity_id = h.entity_id AND o.entity_rev = h.entity_rev\n  WHERE o.operation_sha256 != h.operation_sha256\n`).all();\n// resolve each (drop stale head or bump the rev) before it stalls a flush","typeGuard":null,"tryCatchPattern":"// Thrown inside stampAcked's transaction, captured into lastError — poll:\nconst m = (sync.status().lastError ?? '').match(/cloud sync ack conflict for (\\S+) rev (\\d+)/);\nif (m) {\n  const [, entityId, rev] = m;\n  // inspect sync_entity_heads for (entityId): drop the stale row or touch the\n  // entity to queue a higher-rev snapshot, then let the retry proceed\n}","preventionTips":["Never clone the data directory to another machine without resetting the persisted device id","Keep client versions consistent across devices sharing a hub account (serialization drift causes same-rev hash divergence)","Don't restore old backups over newer sync state; if you must, clear sync_entity_heads and let it re-derive","Never hand-edit sync_entity_heads / sync_content_outbox"],"tags":["sync","conflict","hash","entity-rev","local-state"],"backgroundTag":"revision-conflict","analyzedSha":"e2d1df569a8f04075d40e92461128ece7cf04c82","analyzedAt":"2026-08-20T23:58:13.836Z","contentChangedAt":"2026-08-20T23:58:13.836Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}