{"record":{"id":"649a77ce8e217840","repo":"t8y2/dbx","slug":"etcd-cas-conflict","errorCode":"ETCD_CAS_CONFLICT","errorMessage":"ETCD_CAS_CONFLICT: key changed after it was loaded","messagePattern":"ETCD_CAS_CONFLICT: key changed after it was loaded","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"agents/drivers/etcd-go/kv.go","lineNumber":294,"sourceCode":"\t\tleaseID = clientv3.LeaseID(*leaseValue)\n\t}\n\n\trevision, err := func() (int64, error) {\n\t\tif expectedModRevision != nil || expectedCreateRevision != nil {\n\t\t\tvar comparisons []clientv3.Cmp\n\t\t\tif expectedModRevision != nil {\n\t\t\t\tcomparisons = append(comparisons, clientv3.Compare(clientv3.ModRevision(key), \"=\", *expectedModRevision))\n\t\t\t}\n\t\t\tif expectedCreateRevision != nil {\n\t\t\t\tcomparisons = append(comparisons, clientv3.Compare(clientv3.CreateRevision(key), \"=\", *expectedCreateRevision))\n\t\t\t}\n\t\t\ttxn := client.Txn(ctx).If(comparisons...).Then(clientv3.OpPut(key, value, clientv3.WithLease(leaseID)))\n\t\t\tresponse, err := txn.Commit()\n\t\t\tif err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t\tif !response.Succeeded {\n\t\t\t\treturn 0, errors.New(\"ETCD_CAS_CONFLICT: key changed after it was loaded\")\n\t\t\t}\n\t\t\treturn response.Header.Revision, nil\n\t\t}\n\t\tresponse, err := client.Put(ctx, key, value, clientv3.WithLease(leaseID))\n\t\tif err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t\treturn response.Header.Revision, nil\n\t}()\n\tif err != nil && grantedLeaseID != 0 {\n\t\t_, _ = client.Lease.Revoke(context.Background(), grantedLeaseID)\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn map[string]any{\"revision\": longString(revision)}, nil\n}\n","sourceCodeStart":276,"sourceCodeEnd":312,"githubUrl":"https://github.com/t8y2/dbx/blob/c0390bff16418b651f4728520d99adf8ce48829a/agents/drivers/etcd-go/kv.go#L276-L312","documentation":"Optimistic-concurrency (compare-and-swap) failure thrown by put at kv.go:294. When put is called with expectedModRevision and/or expectedCreateRevision, the driver wraps the write in an etcd transaction whose If condition asserts the key's current revision(s) still match. If another writer changed the key between your read and your write, response.Succeeded is false and this error is returned; the write is NOT applied.","triggerScenarios":"Calling put with expectedModRevision (or expectedCreateRevision) whose value no longer matches the key's live MVCC revision because a concurrent put/delete on the same key landed first.","commonSituations":"Multiple instances/workers doing read-modify-write on a shared config key; a background reconciler updating the key between your GET and your PUT; retries replaying an old expectedModRevision after a first successful write.","solutions":["Re-read the key (get returns metadata with the current mod revision) and retry the put with the fresh expectedModRevision.","Use an exponential-backoff retry loop around read-then-put, since CAS contention is expected under concurrency.","If strict CAS is unnecessary, drop expectedModRevision/expectedCreateRevision and do a plain put.","Reduce contention by sharding keys or serializing writes through a single owner (lease-based leader)."],"exampleFix":"// before\nput(session, key, value, { expectedModRevision: staleRev })\n// after\nfor (let i = 0; i < 5; i++) {\n  const cur = get(session, key, { metadataOnly: true });\n  try {\n    return put(session, key, value, { expectedModRevision: cur.metadata.modRevision });\n  } catch (e) {\n    if (!String(e).includes('ETCD_CAS_CONFLICT')) throw e;\n  }\n}","handlingStrategy":"retry","validationCode":"const cur = get(session, key, { metadataOnly: true });\nconst expected = cur.found ? cur.metadata.modRevision : 0; // capture fresh CAS baseline immediately before put","typeGuard":"function isCasConflict(e) { return String(e && e.message || e).includes('ETCD_CAS_CONFLICT'); }","tryCatchPattern":"for (let attempt = 0; attempt < 5; attempt++) {\n  try { return put(session, key, value, { expectedModRevision: currentModRev(key) }); }\n  catch (e) { if (!isCasConflict(e) || attempt === 4) throw e; }\n}","preventionTips":["Always re-read the revision immediately before a CAS put; never reuse stale expectedModRevision across retries.","Use jittered exponential backoff on CAS retries.","Make writes idempotent so a conflict after partial success is harmless.","Serialize writers to hot keys via leader election or a lock key."],"tags":["etcd","cas","optimistic-concurrency","txn"],"backgroundTag":"cas-write-conflict","analyzedSha":"c0390bff16418b651f4728520d99adf8ce48829a","analyzedAt":"2026-09-05T23:05:10.900Z","contentChangedAt":"2026-09-05T23:05:10.900Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}