thedotmack/claude-mem · error · Error
revision_hash_conflict
Error message
revision_hash_conflict:${body.id}:${body.entity_rev} What it means
Idempotency/hash guard in SyncHubDO.pushOps: the op's entity_rev equals the current head revision but its operation_sha256 differs from the hash stored at that head — two different operations claim the same revision, which is a hash conflict, not a replay. The op is refused to protect the integrity of the canonical op log.
Solutions
- Regenerate the op with a fresh, strictly greater entity_rev and re-push
- Ensure the client hashes canonical operation bytes with the exact same algorithm as the hub
- Never reuse a revision number: always derive from the latest observed head plus one increment
Example fix
// before const rev = nextRev(localCounter); // may collide // after const rev = bumpRevision(await fetchEntityHead(entity.id).entity_rev);
Defensive patterns
Strategy: retry
Validate before calling
const head = await fetchEntityHead(entityId);
if (op.entity_rev === head.entity_rev && hashCanonical(op) !== head.operation_sha256) {
op = { ...op, entity_rev: bumpRevision(head.entity_rev) };
} Try / catch
try {
return await hub.push(deviceId, ops);
} catch (e) {
if (String(e).startsWith('revision_hash_conflict:')) return rePushWithBumpedRevision(e, ops);
throw e;
} Prevention
- Never reuse revision numbers: derive from the latest head
- Use the exact canonical hashing algorithm the hub expects
- For concurrent editors, prefer random/Lamport-style revision tiebreakers
When it happens
Trigger: Two devices independently generate the same entity_rev (e.g. both compute rev 5) with different content; non-deterministic content hashing client-side; a rebased op colliding with an already-acked revision.
Common situations: Divergent offline edits that both pick the same next revision, custom revision schemes that reuse counters after a restore, hash algorithm change between client versions.
Understand the failure class
Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.
Related errors
- cloud sync ack conflict for
- stale_revision: : <
- sync hub push: 200 response acknowledgment multiplicity…
- sync hub push: 200 response contains an extra or mismatched…
- sync hub push: acknowledgment seq exceeds head_seq
AI-assisted analysis of thedotmack/claude-mem@d8bc9755e7 (2026-09-17).
Data as JSON: /api/errors/78a23a61f5bbd06d.
Report an issue: GitHub.
Appendix: source
Thrown at workers/sync-hub/src/do/SyncHub.ts:417
const nowDecimal = String(now);
const headBefore = this.headSeq();
const acked: AckedOp[] = [];
try {
this.ctx.storage.transactionSync(() => {
this.touchDevice(deviceId, normalizeDeviceName(deviceName), now);
for (const row of rows) {
const body = row.body;
const head = sql.exec<HeadRow>(
`SELECT entity_rev, operation_sha256, deleted, CAST(seq AS TEXT) AS seq
FROM entity_heads WHERE entity_id = ?`,
body.id,
).toArray()[0];
if (head) {
const order = compareCanonicalDecimals(body.entity_rev, head.entity_rev);
if (order < 0) throw invalid(`stale_revision:${body.id}:${body.entity_rev}<${head.entity_rev}`);
if (order === 0) {
if (row.operationSha256 !== head.operation_sha256) {
throw invalid(`revision_hash_conflict:${body.id}:${body.entity_rev}`);
}
acked.push({
id: body.id,
kind: body.kind,
origin_local_id: body.origin_local_id,
entity_rev: body.entity_rev,
operation_sha256: head.operation_sha256,
seq: String(head.seq),
});
continue;
}
}
const seq = incrementCanonicalDecimal(this.headSeq());
sql.exec(
`INSERT INTO canonical_ops
(seq, entity_id, kind, origin_device_id, origin_local_id, entity_rev,
operation_sha256, body, deleted, server_ts)View on GitHub (pinned to d8bc9755e7)