etcd-io/etcd · error
unexpected sort in watch
Error message
unexpected sort in watch
What it means
OpWatch panics with 'unexpected sort in watch' when any WithSort* option is applied to a watch. Watches stream events in occurrence order; result sorting only applies to range queries, so OpWatch rejects sort options up front. The panic is thrown during Op construction, before any RPC.
Source
Thrown at client/v3/op.go:347
// 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 {
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)
}
}
View on GitHub (pinned to f744d457f4)
Solutions
- Remove the WithSort* option from the watch call
- Apply sorting client-side to the initial Get used to seed state; rely on event order for the watch
- Maintain separate builders: scan options (sort/limit/serializable) vs watch options (WithPrefix, WithPrevKV, WithProgressNotify, WithCreatedNotify, WithFilter*, WithRev, WithFragment)
- Write a unit test that constructs all watch ops used by your app to catch option leaks in CI
Example fix
// before
op := clientv3.OpWatch("k", clientv3.WithSort(clientv3.SortByModRevision, clientv3.SortAscend))
// after
seed := clientv3.OpGet("k", clientv3.WithSort(clientv3.SortByModRevision, clientv3.SortAscend))
wop := clientv3.OpWatch("k") Defensive patterns
Strategy: validation
Validate before calling
// Sort on the seeding read; sort watch events client-side if needed.
seed := clientv3.OpGet("k", clientv3.WithSort(clientv3.SortByModRevision, clientv3.SortAscend))
wop := clientv3.OpWatch("k") Prevention
- Only reads accept WithSort
- Rely on event order for watches
- Separate scan builders from watch builders
When it happens
Trigger: Calling clientv3.OpWatch(key, clientv3.WithSort(...)) or a WithSort* variant; passing an option list assembled for an ordered scan (e.g. WithSort(SortByCreateRevision) for history listing) to a watch on the same key.
Common situations: UI backends that list keys sorted and then subscribe for updates, sharing one options builder; copy-paste from a Get; upgrading code from polling-with-sort to watching without removing sort options.
Related errors
- unexpected sort in put
- unexpected lease in watch
- unexpected limit in watch
- unexpected serializable in watch
- unexpected countOnly in watch
AI-assisted analysis of etcd-io/etcd@f744d457f4 (2026-08-15).
Data as JSON: /api/errors/33b128ed2d78a5ff.
Report an issue: GitHub.