{"record":{"id":"4f8dfa5c06644b13","repo":"etcd-io/etcd","slug":"withprefix-and-withfromkey-cannot-be-set-at-th","errorCode":null,"errorMessage":"`WithPrefix` and `WithFromKey` cannot be set at the same time, choose one","messagePattern":"`WithPrefix` and `WithFromKey` cannot be set at the same time, choose one","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/v3/op.go","lineNumber":260,"sourceCode":"\t\tfor _, tOp := range op.elseOps {\n\t\t\tif tOp.isWrite() {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t\treturn false\n\t}\n\treturn op.t != tRange\n}\n\nfunc NewOp() *Op {\n\treturn &Op{key: []byte(\"\")}\n}\n\n// OpGet returns \"get\" operation based on given key and operation options.\nfunc OpGet(key string, opts ...OpOption) Op {\n\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: tRange, key: []byte(key)}\n\tret.applyOpts(opts)\n\treturn ret\n}\n\n// OpDelete returns \"delete\" operation based on given key and operation options.\nfunc OpDelete(key string, opts ...OpOption) Op {\n\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:","sourceCodeStart":242,"sourceCodeEnd":278,"githubUrl":"https://github.com/etcd-io/etcd/blob/f744d457f484e9f748a0700b48ef96dcf792df33/client/v3/op.go#L242-L278","documentation":"clientv3.OpGet panics when the option list contains both WithPrefix and WithFromKey. The two options compute conflicting range semantics: WithPrefix sets an end key derived from the key prefix (keys up to the next lexicographic prefix), while WithFromKey treats the key as the inclusive lower bound with end \\0 (all keys >= key). etcd cannot honor both, so OpGet fails fast instead of silently picking one.","triggerScenarios":"clientv3.OpGet(\"/foo\", clientv3.WithPrefix(), clientv3.WithFromKey()); also indirect via cli.Get(ctx, \"/foo\", clientv3.WithPrefix(), clientv3.WithFromKey()) which calls OpGet internally; commonly happens when options are assembled into a slice and conditionally appended (e.g. WithPrefix added when key ends in '/', WithFromKey added elsewhere).","commonSituations":"A generic helper like getRange(key, prefixMode) that appends both options from different flags; refactoring WithFromKey usage into code that already had WithPrefix; behavior differences — WithFromKey ignores trailing '/' semantics that WithPrefix has, so teams combine them trying to get '>= key, matching spirit of prefix'.","solutions":["Choose one: WithPrefix() for all keys sharing the prefix, or WithFromKey() for all keys lexicographically >= key.","In option-assembly code, use else-if / exclusive branches so the two can never both be appended.","If you need 'prefix and beyond', that is exactly WithFromKey alone — drop WithPrefix.","Add a unit assertion on your assembled []OpOption (IsOptsWithPrefix && IsOptsWithFromKey) before issuing the op."],"exampleFix":"// before\nopts := []clientv3.OpOption{clientv3.WithLimit(10)}\nif strings.HasSuffix(key, \"/\") {\n    opts = append(opts, clientv3.WithPrefix())\n}\nif fromKey {\n    opts = append(opts, clientv3.WithFromKey()) // both set when fromKey && suffix '/'\ngo cli.Get(ctx, key, opts...) // panics\n\n// after\nif fromKey {\n    opts = append(opts, clientv3.WithFromKey())\n} else if strings.HasSuffix(key, \"/\") {\n    opts = append(opts, clientv3.WithPrefix())\n}","handlingStrategy":"validation","validationCode":"// clientv3 exposes the same predicates the library uses:\nif clientv3.IsOptsWithPrefix(opts) && clientv3.IsOptsWithFromKey(opts) {\n    return errors.New(\"WithPrefix and WithFromKey are mutually exclusive\")\n}\nresp, err := cli.Get(ctx, key, opts...)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Build range options in an if/else chain so WithPrefix and WithFromKey can never both be appended.","Remember the semantics: WithPrefix = subtree under prefix; WithFromKey = everything >= key.","Validate assembled []OpOption with clientv3.IsOptsWithPrefix/IsOptsWithFromKey in generic helpers."],"tags":["go","etcd","clientv3","op","range","panic","conflicting-options"],"backgroundTag":null,"analyzedSha":"f744d457f484e9f748a0700b48ef96dcf792df33","analyzedAt":"2026-08-15T09:39:50.079Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}