etcd-io/etcd · error
unexpected create revision filter in put
Error message
unexpected create revision filter in put
What it means
OpPut panics with 'unexpected create revision filter in put' when WithMinCreateRev or WithMaxCreateRev is applied to a put. Create-revision filters limit range results to keys created within a revision window; they are query-only and rejected by OpPut's validation switch. The panic fires synchronously during Op construction, before any network activity.
Source
Thrown at client/v3/op.go:320
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:
panic("unexpected limit in put")
case ret.rev != 0:
panic("unexpected revision in put")
case ret.sort != nil:
panic("unexpected sort in put")
case ret.serializable:
panic("unexpected serializable in put")
case ret.countOnly:
panic("unexpected countOnly in put")
case ret.minModRev != 0, ret.maxModRev != 0:
panic("unexpected mod revision filter in put")
case ret.minCreateRev != 0, ret.maxCreateRev != 0:
panic("unexpected create revision filter in put")
case ret.filterDelete, ret.filterPut:
panic("unexpected filter in put")
case ret.createdNotify:
panic("unexpected createdNotify in put")
}
return ret
}
// OpTxn returns "txn" operation based on given transaction conditions.
func OpTxn(cmps []Cmp, thenOps []Op, elseOps []Op) Op {
clonedCmps := make([]Cmp, len(cmps))
for i := range cmps {
clonedCmps[i] = cmps[i].Clone()
}
return Op{t: tTxn, cmps: clonedCmps, thenOps: thenOps, elseOps: elseOps}
}
func OpWatch(key string, opts ...OpOption) Op {View on GitHub (pinned to f744d457f4)
Solutions
- Remove WithMinCreateRev/WithMaxCreateRev from the put
- If the intent is 'write only if the key exists/doesn't exist', use a Txn with If(CreateRevision(key) ... ) instead
- Keep option assembly per-operation-type: buildOptsGet() vs buildOptsPut()
- Add a guard test that panics loudly in CI for every op your code constructs
Example fix
// before
op := clientv3.OpPut("k", "v", clientv3.WithMaxCreateRev(100))
// after (write only if key already exists)
resp, err := cli.Txn(ctx).
If(clientv3.Compare(clientv3.CreateRevision("k"), ">", 0)).
Then(clientv3.OpPut("k", "v")).
Commit() Defensive patterns
Strategy: validation
Validate before calling
// Existence-gated write belongs in the Txn condition, not a filter option:
resp, err := cli.Txn(ctx).
If(clientv3.Compare(clientv3.CreateRevision("k"), "=", 0)). // key absent
Then(clientv3.OpPut("k", "v")).
Commit() Prevention
- Use CreateRevision/ModRevision Compare for conditional writes
- Never pass With*CreateRev to OpPut
- One option builder per operation type
When it happens
Trigger: Calling clientv3.OpPut(key, val, clientv3.WithMinCreateRev(n)) or WithMaxCreateRev(n); reusing an options slice built for a creation-filtered Get in a subsequent put; generic wrappers that merge user-supplied options into every operation type.
Common situations: Audit/listing code that filters keys by creation revision and later writes metadata back with the same options; copy-paste drift between Get and Put calls; refactors that moved option assembly into one shared function.
Related errors
- unexpected mod revision filter in put
- unexpected sort in put
- unexpected serializable in put
- unexpected countOnly in put
- unexpected filter in put
AI-assisted analysis of etcd-io/etcd@f744d457f4 (2026-08-15).
Data as JSON: /api/errors/affcb6e2154ea9b3.
Report an issue: GitHub.