etcd-io/etcd · error
unexpected countOnly in watch
Error message
unexpected countOnly in watch
What it means
OpWatch panics with 'unexpected countOnly in watch' when WithCountOnly is applied to a watch. CountOnly collapses a range response to just the count of matching keys; a watch returns an event stream, so a count-only mode is rejected. OpWatch validates its option set and panics at construction time.
Source
Thrown at client/v3/op.go:351
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)
}
}
// OpOption configures Operations like Get, Put, Delete.
type OpOption func(*Op)
// WithLease attaches a lease ID to a key in 'Put' request.View on GitHub (pinned to f744d457f4)
Solutions
- Remove WithCountOnly from the watch call
- If you need both a current count and change notifications, do a count Get plus a plain watch as two separate ops
- Build watch options explicitly (WithPrefix, WithPrevKV, ...) rather than reusing read options
- Unit-test every op your binary constructs; these panics are cheap to catch in CI
Example fix
// before
op := clientv3.OpWatch("prefix/", clientv3.WithPrefix(), clientv3.WithCountOnly())
// after
cntOp := clientv3.OpGet("prefix/", clientv3.WithPrefix(), clientv3.WithCountOnly())
wop := clientv3.OpWatch("prefix/", clientv3.WithPrefix()) Defensive patterns
Strategy: validation
Validate before calling
// Count and watch are two distinct ops:
cnt := clientv3.OpGet("p/", clientv3.WithPrefix(), clientv3.WithCountOnly())
wop := clientv3.OpWatch("p/", clientv3.WithPrefix()) Prevention
- Keep count flags out of subscriptions
- Recompute counts from watch events if needed
- Test watch construction paths
When it happens
Trigger: Calling clientv3.OpWatch(key, clientv3.WithCountOnly()); reusing options from an existence/count check (WithCountOnly + WithPrefix) when subscribing to the same prefix; generic wrappers that forward a user's entire option list into both counts and watches.
Common situations: Health-check or quota code that counts prefix members and then watches the prefix for changes; shared option slices across monitoring paths; refactors moving from poll-count to watch without pruning options.
Related errors
- unexpected countOnly in put
- unexpected lease in watch
- unexpected limit in watch
- unexpected sort in watch
- unexpected serializable in watch
AI-assisted analysis of etcd-io/etcd@f744d457f4 (2026-08-15).
Data as JSON: /api/errors/441e8729f714834f.
Report an issue: GitHub.