{"record":{"id":"92d246d5ccd01702","repo":"etcd-io/etcd","slug":"unexpected-limit-in-put","errorCode":null,"errorMessage":"unexpected limit in put","messagePattern":"unexpected limit in put","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/v3/op.go","lineNumber":308,"sourceCode":"\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:\n\t\tpanic(\"unexpected range in put\")\n\tcase ret.limit != 0:\n\t\tpanic(\"unexpected limit in put\")\n\tcase ret.rev != 0:\n\t\tpanic(\"unexpected revision in put\")\n\tcase ret.sort != nil:\n\t\tpanic(\"unexpected sort in put\")\n\tcase ret.serializable:\n\t\tpanic(\"unexpected serializable in put\")\n\tcase ret.countOnly:\n\t\tpanic(\"unexpected countOnly in put\")\n\tcase ret.minModRev != 0, ret.maxModRev != 0:\n\t\tpanic(\"unexpected mod revision filter in put\")\n\tcase ret.minCreateRev != 0, ret.maxCreateRev != 0:\n\t\tpanic(\"unexpected create revision filter in put\")\n\tcase ret.filterDelete, ret.filterPut:\n\t\tpanic(\"unexpected filter in put\")\n\tcase ret.createdNotify:\n\t\tpanic(\"unexpected createdNotify in put\")\n\t}\n\treturn ret","sourceCodeStart":290,"sourceCodeEnd":326,"githubUrl":"https://github.com/etcd-io/etcd/blob/f744d457f484e9f748a0700b48ef96dcf792df33/client/v3/op.go#L290-L326","documentation":"OpPut rejects WithLimit because limit bounds the number of results of a range read; a put returns no result set. After applyOpts, the validation switch sees ret.limit != 0 and panics with \"unexpected limit in put\". Like the other put guards, it exists to surface option misuse (typically a Get's option list reused for a Put) at construction time.","triggerScenarios":"clientv3.OpPut(\"/k\", \"v\", clientv3.WithLimit(10)); forwarding pagination options from a listing Get into the subsequent write-back Put; shared []OpOption bundles.","commonSituations":"Batch-processing loops that list with WithLimit and write results back using the same options variable; generic CRUD wrappers with one options parameter for all methods.","solutions":["Remove WithLimit from Put calls.","Keep the Get's pagination options in a distinct variable from the Put's options (e.g. getOpts vs putOpts).","If you intended bounded writes, bound them client-side by chunking keys into Txn batches."],"exampleFix":"// before\nopts := []clientv3.OpOption{clientv3.WithLimit(100), clientv3.WithPrefix()}\ncli.Get(ctx, \"/q/\", opts...)\n_, err := cli.Put(ctx, \"/q/done\", \"1\", opts...) // panics: unexpected limit in put\n\n// after\ngetOpts := []clientv3.OpOption{clientv3.WithLimit(100), clientv3.WithPrefix()}\ncli.Get(ctx, \"/q/\", getOpts...)\n_, err := cli.Put(ctx, \"/q/done\", \"1\")","handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":["WithLimit is a Get pagination option; a Put has no result set to cap.","After a paginated Get, write back with a fresh, empty option list.","Name option variables by purpose (getOpts vs putOpts) so reuse bugs are visible in review."],"tags":["go","etcd","clientv3","put","limit","panic","invalid-option"],"backgroundTag":null,"analyzedSha":"f744d457f484e9f748a0700b48ef96dcf792df33","analyzedAt":"2026-08-15T09:39:50.079Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}