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

  1. For a pre-delete count, use cli.Get(ctx, prefix, clientv3.WithPrefix(), clientv3.WithCountOnly()) and read resp.Count — a Get, not a Delete.
  2. 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

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


AI-assisted analysis of etcd-io/etcd@f744d457f4 (2026-08-15). Data as JSON: /api/errors/be020e0b48d74a10. Report an issue: GitHub.