etcd-io/etcd · error
unexpected mod revision filter in delete
Error message
unexpected mod revision filter in delete
What it means
OpDelete rejects WithMinModRev / WithMaxModRev. Mod-rev filters restrict range reads to keys modified within a revision window; delete ranges operate on current state and cannot be filtered that way. The validation switch checks ret.minModRev != 0 || ret.maxModRev != 0 and panics with "unexpected mod revision filter in delete".
Source
Thrown at client/v3/op.go:289
panic("`WithPrefix` and `WithFromKey` cannot be set at the same time, choose one")
}
ret := Op{t: tDeleteRange, key: []byte(key)}
ret.applyOpts(opts)
switch {
case ret.leaseID != 0:
panic("unexpected lease in delete")
case ret.limit != 0:
panic("unexpected limit in delete")
case ret.rev != 0:
panic("unexpected revision in delete")
case ret.sort != nil:
panic("unexpected sort in delete")
case ret.serializable:
panic("unexpected serializable in delete")
case ret.countOnly:
panic("unexpected countOnly in delete")
case ret.minModRev != 0, ret.maxModRev != 0:
panic("unexpected mod revision filter in delete")
case ret.minCreateRev != 0, ret.maxCreateRev != 0:
panic("unexpected create revision filter in delete")
case ret.filterDelete, ret.filterPut:
panic("unexpected filter in delete")
case ret.createdNotify:
panic("unexpected createdNotify in delete")
}
return ret
}
// OpPut returns "put" operation based on given key-value and operation options.
func OpPut(key, val string, opts ...OpOption) Op {
ret := Op{t: tPut, key: []byte(key), val: []byte(val)}
ret.applyOpts(opts)
switch {
case ret.end != nil:
panic("unexpected range in put")
case ret.limit != 0:View on GitHub (pinned to f744d457f4)
Solutions
- Do the filtered listing with Get + WithMinModRev/WithMaxModRev, then delete the returned keys explicitly (Txn of OpDelete per key or batches).
- If deletion must be conditional on the key's revision, use a Txn If with CompareModified instead of a filter option.
- Remove the mod-rev options from the Delete call.
Example fix
// before
_, err := cli.Delete(ctx, "/tmp/", clientv3.WithPrefix(), clientv3.WithMaxModRev(watermarkRev)) // panics
// after
gr, err := cli.Get(ctx, "/tmp/", clientv3.WithPrefix(), clientv3.WithMaxModRev(watermarkRev))
ops := make([]clientv3.Op, 0, len(gr.Kvs))
for _, kv := range gr.Kvs {
ops = append(ops, clientv3.OpDelete(string(kv.Key)))
}
_, err = cli.Txn(ctx).Then(ops...).Commit() Defensive patterns
Strategy: validation
Prevention
- Mod/create-revision filters select keys for reads; filter client-side, then delete explicit keys.
- Pattern for GC: Get with WithMaxModRev(watermark) -> collect keys -> Txn Then(OpDelete...).
- Never forward a filtered listing's option slice into its cleanup delete.
When it happens
Trigger: clientv3.OpDelete("/k/", clientv3.WithPrefix(), clientv3.WithMaxModRev(someRev)) — e.g. 'delete only entries not touched since rev N'; or option-slice reuse from an incremental-scan Get.
Common situations: Garbage-collection routines that first list stale keys with mod-rev filters and then try to apply the same filter to the purge Delete; sync tooling that carries filter options through one code path for both read and delete.
Related errors
- unexpected create revision filter in delete
- unexpected lease in delete
- unexpected limit in delete
- unexpected revision in delete
- unexpected sort in delete
AI-assisted analysis of etcd-io/etcd@f744d457f4 (2026-08-15).
Data as JSON: /api/errors/3dd8bcd353935030.
Report an issue: GitHub.