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".
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)
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 lease in delete
- unexpected limit in delete
- unexpected revision in delete
- unexpected sort in delete
- unexpected serializable in delete
AI-assisted analysis of etcd-io/etcd@f744d457f4 (2026-08-15).
Data as JSON: /api/errors/be020e0b48d74a10.
Report an issue: GitHub.