etcd-io/etcd · error
unexpected serializable in watch
Error message
unexpected serializable in watch
What it means
OpWatch panics with 'unexpected serializable in watch' when WithSerializable is applied to a watch. Serializable reads may be served stale from followers, which contradicts the guarantee watches need (events from the linearizable store); the option is therefore rejected by OpWatch's validation. The panic fires at construction time, before any stream is established.
Source
Thrown at client/v3/op.go:349
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)
}
}
// OpOption configures Operations like Get, Put, Delete.
type OpOption func(*Op)View on GitHub (pinned to f744d457f4)
Solutions
- Remove WithSerializable from the watch call
- If stale reads are acceptable, apply WithSerializable only to OpGet calls in your read helper
- Audit central option factories for blanket-applied flags; scope them per operation type
- Cover watch construction in unit tests so the panic surfaces during CI
Example fix
// before
op := clientv3.OpWatch("k", clientv3.WithSerializable())
// after
wop := clientv3.OpWatch("k")
getOp := clientv3.OpGet("k", clientv3.WithSerializable()) // stale reads OK here Defensive patterns
Strategy: validation
Validate before calling
// Serializable reads only:
getOp := clientv3.OpGet("k", clientv3.WithSerializable())
wop := clientv3.OpWatch("k") Prevention
- Never add consistency flags to watches
- Audit 'reduce load' tunings applied globally
- Per-operation-type option builders
When it happens
Trigger: Calling clientv3.OpWatch(key, clientv3.WithSerializable()); sharing an options slice built for stale-tolerant reads (common in read-heavy caches) with watch setup; helper functions that stamp WithSerializable on every operation to 'reduce load'.
Common situations: Performance tuning passes that blanket-apply WithSerializable across all client calls; multi-region deployments relying on follower reads where the same option list is reused for subscriptions; copy-paste between a Get and a Watch block.
Related errors
- unexpected serializable in put
- unexpected lease in watch
- unexpected limit in watch
- unexpected sort in watch
- unexpected countOnly in watch
AI-assisted analysis of etcd-io/etcd@f744d457f4 (2026-08-15).
Data as JSON: /api/errors/217824f84479d2d2.
Report an issue: GitHub.