{"record":{"id":"90adc8e3374d968c","repo":"etcd-io/etcd","slug":"unexpected-range-in-put","errorCode":null,"errorMessage":"unexpected range in put","messagePattern":"unexpected range in put","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/v3/op.go","lineNumber":306,"sourceCode":"\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:\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\")","sourceCodeStart":288,"sourceCodeEnd":324,"githubUrl":"https://github.com/etcd-io/etcd/blob/f744d457f484e9f748a0700b48ef96dcf792df33/client/v3/op.go#L288-L324","documentation":"clientv3.OpPut (and cli.Put, which delegates to it) panics with \"unexpected range in put\" when the option list sets an end key: WithRange, WithFromKey, or WithPrefix all set op.end. A put writes a single key; a range end is inapplicable, so the constructor rejects it rather than silently ignoring it.","triggerScenarios":"clientv3.OpPut(\"/k\", \"v\", clientv3.WithRange(\"/z\")) or reusing a Get/Delete option slice (containing WithPrefix/WithFromKey/WithRange) for a Put; generic helper APIs like do(opType, key, val, opts...) that forward the same options to every operation kind.","commonSituations":"Read-modify-write flows where the read used WithPrefix and the option slice is reused; utility wrappers that accept OpOption variadics for both Put and Get; copy-paste between a bulk delete and a status-marker put.","solutions":["Remove WithRange/WithPrefix/WithFromKey from Put calls — a put targets exactly one key.","To put many keys, build []Op of OpPut per key and submit via cli.Txn(ctx).Then(ops...).Commit().","Separate option slices per operation kind in shared helpers."],"exampleFix":"// before\n_, err := cli.Put(ctx, \"/cfg/\", newVal, clientv3.WithPrefix()) // panics: unexpected range in put\n\n// after\n_, err := cli.Put(ctx, \"/cfg/defaults\", newVal)","handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":["A Put targets exactly one key: WithRange/WithPrefix/WithFromKey never belong on it.","For multi-key writes, build []clientv3.Op of OpPut and submit via Txn Then(...).","In generic helpers, partition options into getOpts/putOpts/delOpts at the boundary."],"tags":["go","etcd","clientv3","put","range","panic","invalid-option"],"backgroundTag":null,"analyzedSha":"f744d457f484e9f748a0700b48ef96dcf792df33","analyzedAt":"2026-08-15T09:39:50.079Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}