etcd-io/etcd · error
unexpected countOnly in delete
Error message
unexpected countOnly in delete
What it means
OpDelete rejects WithCountOnly. countOnly asks the server to return just the number of matching keys for a range query; a delete response already returns only the deleted count, so the flag is redundant and unsupported. When applyOpts sets ret.countOnly, the constructor panics with "unexpected countOnly in delete".
Solutions
- For a pre-delete count, use cli.Get(ctx, prefix, clientv3.WithPrefix(), clientv3.WithCountOnly()) and read resp.Count — a Get, not a Delete.
- Remove WithCountOnly from Delete calls; resp.Deleted in the DeleteResponse already carries the count.
Example fix
// before (trying to preview delete impact)
resp, err := cli.Delete(ctx, "/stale/", clientv3.WithPrefix(), clientv3.WithCountOnly()) // panics
// after
gr, err := cli.Get(ctx, "/stale/", clientv3.WithPrefix(), clientv3.WithCountOnly())
fmt.Println("will delete", gr.Count, "keys") Defensive patterns
Strategy: validation
Prevention
- Preview counts with Get+WithPrefix+WithCountOnly (resp.Count), never with Delete.
- DeleteResponse.Deleted already reports how many keys were removed — no pre-flight delete needed.
- Read the OpDelete docstring's implicit contract: only key/range/prevKV-style options apply.
When it happens
Trigger: clientv3.OpDelete("/k/", clientv3.WithPrefix(), clientv3.WithCountOnly()); 'check how many would be deleted' probes that reuse the delete call with countOnly instead of a Get.
Common situations: Pre-flight checks before bulk deletes implemented by copying the delete call and adding WithCountOnly; option forwarding from an existence-count routine.
Related errors
- unexpected create revision filter in delete
- unexpected createdNotify in delete
- unexpected filter in delete
- unexpected lease in delete
- unexpected limit in delete
AI-assisted analysis of etcd-io/etcd@f744d457f4 (2026-08-15).
Data as JSON: /api/errors/be020e0b48d74a10.
Report an issue: GitHub.
Appendix: source
Thrown at client/v3/op.go:287
// WithPrefix and WithFromKey are not supported together
if IsOptsWithPrefix(opts) && IsOptsWithFromKey(opts) {
panic("`WithPrefix` and `WithFromKey` cannot be set at the same time, choose one")
}
ret := Op{t: tDeleteRange, key: []byte(key)}
ret.applyOpts(opts)
switch {
case ret.leaseID != 0:
panic("unexpected lease in delete")
case ret.limit != 0:
panic("unexpected limit in delete")
case ret.rev != 0:
panic("unexpected revision in delete")
case ret.sort != nil:
panic("unexpected sort in delete")
case ret.serializable:
panic("unexpected serializable in delete")
case ret.countOnly:
panic("unexpected countOnly in delete")
case ret.minModRev != 0, ret.maxModRev != 0:
panic("unexpected mod revision filter in delete")
case ret.minCreateRev != 0, ret.maxCreateRev != 0:
panic("unexpected create revision filter in delete")
case ret.filterDelete, ret.filterPut:
panic("unexpected filter in delete")
case ret.createdNotify:
panic("unexpected createdNotify in delete")
}
return ret
}
// OpPut returns "put" operation based on given key-value and operation options.
func OpPut(key, val string, opts ...OpOption) Op {
ret := Op{t: tPut, key: []byte(key), val: []byte(val)}
ret.applyOpts(opts)
switch {
case ret.end != nil:View on GitHub (pinned to f744d457f4)