{"record":{"id":"3dd8bcd353935030","repo":"etcd-io/etcd","slug":"unexpected-mod-revision-filter-in-delete","errorCode":null,"errorMessage":"unexpected mod revision filter in delete","messagePattern":"unexpected mod revision filter in delete","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/v3/op.go","lineNumber":289,"sourceCode":"\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:\n\t\tpanic(\"unexpected range in put\")\n\tcase ret.limit != 0:","sourceCodeStart":271,"sourceCodeEnd":307,"githubUrl":"https://github.com/etcd-io/etcd/blob/f744d457f484e9f748a0700b48ef96dcf792df33/client/v3/op.go#L271-L307","documentation":"OpDelete rejects WithMinModRev / WithMaxModRev. Mod-rev filters restrict range reads to keys modified within a revision window; delete ranges operate on current state and cannot be filtered that way. The validation switch checks ret.minModRev != 0 || ret.maxModRev != 0 and panics with \"unexpected mod revision filter in delete\".","triggerScenarios":"clientv3.OpDelete(\"/k/\", clientv3.WithPrefix(), clientv3.WithMaxModRev(someRev)) — e.g. 'delete only entries not touched since rev N'; or option-slice reuse from an incremental-scan Get.","commonSituations":"Garbage-collection routines that first list stale keys with mod-rev filters and then try to apply the same filter to the purge Delete; sync tooling that carries filter options through one code path for both read and delete.","solutions":["Do the filtered listing with Get + WithMinModRev/WithMaxModRev, then delete the returned keys explicitly (Txn of OpDelete per key or batches).","If deletion must be conditional on the key's revision, use a Txn If with CompareModified instead of a filter option.","Remove the mod-rev options from the Delete call."],"exampleFix":"// before\n_, err := cli.Delete(ctx, \"/tmp/\", clientv3.WithPrefix(), clientv3.WithMaxModRev(watermarkRev)) // panics\n\n// after\ngr, err := cli.Get(ctx, \"/tmp/\", clientv3.WithPrefix(), clientv3.WithMaxModRev(watermarkRev))\nops := make([]clientv3.Op, 0, len(gr.Kvs))\nfor _, kv := range gr.Kvs {\n    ops = append(ops, clientv3.OpDelete(string(kv.Key)))\n}\n_, err = cli.Txn(ctx).Then(ops...).Commit()","handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":["Mod/create-revision filters select keys for reads; filter client-side, then delete explicit keys.","Pattern for GC: Get with WithMaxModRev(watermark) -> collect keys -> Txn Then(OpDelete...).","Never forward a filtered listing's option slice into its cleanup delete."],"tags":["go","etcd","clientv3","delete","mod-revision","filter","panic","invalid-option"],"backgroundTag":null,"analyzedSha":"f744d457f484e9f748a0700b48ef96dcf792df33","analyzedAt":"2026-08-15T09:39:50.079Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}