etcd-io/etcd · error
unexpected mod revision filter in watch
Error message
unexpected mod revision filter in watch
What it means
OpWatch panics with 'unexpected mod revision filter in watch' when WithMinModRev or WithMaxModRev is applied to a watch. Revision-window filters restrict range results to keys modified in a revision range; watches are configured with a start revision (WithRev), not filters, so OpWatch rejects these options. The panic occurs synchronously in OpWatch.
Source
Thrown at client/v3/op.go:353
return Op{t: tTxn, cmps: clonedCmps, thenOps: thenOps, elseOps: elseOps}
}
func OpWatch(key string, opts ...OpOption) Op {
ret := Op{t: tRange, key: []byte(key)}
ret.applyOpts(opts)
switch {
case ret.leaseID != 0:
panic("unexpected lease in watch")
case ret.limit != 0:
panic("unexpected limit in watch")
case ret.sort != nil:
panic("unexpected sort in watch")
case ret.serializable:
panic("unexpected serializable in watch")
case ret.countOnly:
panic("unexpected countOnly in watch")
case ret.minModRev != 0, ret.maxModRev != 0:
panic("unexpected mod revision filter in watch")
case ret.minCreateRev != 0, ret.maxCreateRev != 0:
panic("unexpected create revision filter in watch")
}
return ret
}
func (op *Op) applyOpts(opts []OpOption) {
for _, opt := range opts {
opt(op)
}
}
// OpOption configures Operations like Get, Put, Delete.
type OpOption func(*Op)
// WithLease attaches a lease ID to a key in 'Put' request.
func WithLease(leaseID LeaseID) OpOption {
return func(op *Op) { op.leaseID = leaseID }View on GitHub (pinned to f744d457f4)
Solutions
- Remove WithMinModRev/WithMaxModRev from the watch call
- To resume a watch from a point in history, use WithRev(startRevision) on the watch and optionally WithPrefix for ranges
- Keep the revision-window filters on the catch-up Get only
- Ensure option assembly for watch ops is a separate, minimal builder
Example fix
// before
op := clientv3.OpWatch("k", clientv3.WithMinModRev(500))
// after
catchUp := clientv3.OpGet("k", clientv3.WithMinModRev(500))
wop := clientv3.OpWatch("k", clientv3.WithRev(lastSeenRev+1)) Defensive patterns
Strategy: validation
Validate before calling
// Resume watches with WithRev, not revision filters:
wop := clientv3.OpWatch("k", clientv3.WithRev(lastRev+1)) Prevention
- Use WithRev for historical resume on watches
- Revision-window filters are read-only
- Separate catch-up read options from watch options
When it happens
Trigger: Calling clientv3.OpWatch(key, clientv3.WithMinModRev(n)) or WithMaxModRev(n); converting a filtered historical read into a watch while keeping the filter options; generic sync helpers applying the same filters to reads and watches.
Common situations: Incremental-sync or backup tools that read a mod-revision window then watch for live changes; code sharing one options slice between the catch-up read and the subscription; version upgrades where option lists got merged during refactor.
Related errors
- unexpected create revision filter in watch
- unexpected mod revision filter in put
- unexpected create revision filter in put
- unexpected lease in watch
- unexpected limit in watch
AI-assisted analysis of etcd-io/etcd@f744d457f4 (2026-08-15).
Data as JSON: /api/errors/035894a7e92a96e3.
Report an issue: GitHub.