etcd-io/etcd · error
unexpected limit in delete
Error message
unexpected limit in delete
What it means
OpDelete rejects WithLimit because limit caps the number of results returned by a range query; a delete range has no result set to cap. After applyOpts stores the limit, the post-condition switch sees ret.limit != 0 and panics with "unexpected limit in delete". This guards against options that would be silently ignored (or worse, mis-encoded) in the delete request.
Source
Thrown at client/v3/op.go:279
}
ret := Op{t: tRange, key: []byte(key)}
ret.applyOpts(opts)
return ret
}
// OpDelete returns "delete" operation based on given key and operation options.
func OpDelete(key string, opts ...OpOption) Op {
// 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 retView on GitHub (pinned to f744d457f4)
Solutions
- Drop WithLimit from Delete calls — etcd delete ranges are atomic and unbounded by design.
- For batched deletion, first page through keys with Get+WithLimit, then issue Txn deletes of those explicit keys (accepting it is no longer one atomic range delete).
- Keep pagination options scoped to Get call sites only.
Example fix
// before _, err := cli.Delete(ctx, "/jobs/", clientv3.WithPrefix(), clientv3.WithLimit(500)) // panics // after — atomic full-range delete _, err := cli.Delete(ctx, "/jobs/", clientv3.WithPrefix()) // or explicit batching via Get+Txn if you truly need bounded chunks
Defensive patterns
Strategy: validation
Prevention
- Delete ranges cannot be capped; do not port Get pagination options onto Delete.
- For bounded deletion, Get a page of keys then delete those explicit keys in a Txn.
- Scope WithLimit strictly to Get call sites.
When it happens
Trigger: clientv3.OpDelete("/jobs/", clientv3.WithPrefix(), clientv3.WithLimit(100)) — typically from an attempt to 'delete in batches'; or cli.Delete(ctx, k, sharedOpts...) where sharedOpts was built for pagination of a Get.
Common situations: Developers wanting bounded/batched deletes to limit server load copy the Get pagination pattern onto Delete; generic option forwarding between list and purge code paths.
Related errors
- unexpected lease in delete
- unexpected revision in delete
- unexpected sort in delete
- unexpected serializable in delete
- unexpected countOnly in delete
AI-assisted analysis of etcd-io/etcd@f744d457f4 (2026-08-15).
Data as JSON: /api/errors/b1f7a4c470b8fa6c.
Report an issue: GitHub.