{"record":{"id":"be020e0b48d74a10","repo":"etcd-io/etcd","slug":"unexpected-countonly-in-delete","errorCode":null,"errorMessage":"unexpected countOnly in delete","messagePattern":"unexpected countOnly in delete","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/v3/op.go","lineNumber":287,"sourceCode":"\t// WithPrefix and WithFromKey are not supported together\n\tif IsOptsWithPrefix(opts) && IsOptsWithFromKey(opts) {\n\t\tpanic(\"`WithPrefix` and `WithFromKey` cannot be set at the same time, choose one\")\n\t}\n\tret := Op{t: tDeleteRange, key: []byte(key)}\n\tret.applyOpts(opts)\n\tswitch {\n\tcase ret.leaseID != 0:\n\t\tpanic(\"unexpected lease in delete\")\n\tcase ret.limit != 0:\n\t\tpanic(\"unexpected limit in delete\")\n\tcase ret.rev != 0:\n\t\tpanic(\"unexpected revision in delete\")\n\tcase ret.sort != nil:\n\t\tpanic(\"unexpected sort in delete\")\n\tcase ret.serializable:\n\t\tpanic(\"unexpected serializable in delete\")\n\tcase ret.countOnly:\n\t\tpanic(\"unexpected countOnly in delete\")\n\tcase ret.minModRev != 0, ret.maxModRev != 0:\n\t\tpanic(\"unexpected mod revision filter in delete\")\n\tcase ret.minCreateRev != 0, ret.maxCreateRev != 0:\n\t\tpanic(\"unexpected create revision filter in delete\")\n\tcase ret.filterDelete, ret.filterPut:\n\t\tpanic(\"unexpected filter in delete\")\n\tcase ret.createdNotify:\n\t\tpanic(\"unexpected createdNotify in delete\")\n\t}\n\treturn ret\n}\n\n// OpPut returns \"put\" operation based on given key-value and operation options.\nfunc OpPut(key, val string, opts ...OpOption) Op {\n\tret := Op{t: tPut, key: []byte(key), val: []byte(val)}\n\tret.applyOpts(opts)\n\tswitch {\n\tcase ret.end != nil:","sourceCodeStart":269,"sourceCodeEnd":305,"githubUrl":"https://github.com/etcd-io/etcd/blob/f744d457f484e9f748a0700b48ef96dcf792df33/client/v3/op.go#L269-L305","documentation":"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\".","triggerScenarios":"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.","commonSituations":"Pre-flight checks before bulk deletes implemented by copying the delete call and adding WithCountOnly; option forwarding from an existence-count routine.","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."],"exampleFix":"// before (trying to preview delete impact)\nresp, err := cli.Delete(ctx, \"/stale/\", clientv3.WithPrefix(), clientv3.WithCountOnly()) // panics\n\n// after\ngr, err := cli.Get(ctx, \"/stale/\", clientv3.WithPrefix(), clientv3.WithCountOnly())\nfmt.Println(\"will delete\", gr.Count, \"keys\")","handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["go","etcd","clientv3","delete","count-only","panic","invalid-option"],"backgroundTag":null,"analyzedSha":"f744d457f484e9f748a0700b48ef96dcf792df33","analyzedAt":"2026-08-15T09:39:50.079Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}