{"record":{"id":"47b59d59bdfc6b06","repo":"etcd-io/etcd","slug":"unexpected-filter-in-delete","errorCode":null,"errorMessage":"unexpected filter in delete","messagePattern":"unexpected filter in delete","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/v3/op.go","lineNumber":293,"sourceCode":"\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:\n\t\tpanic(\"unexpected limit in put\")\n\tcase ret.rev != 0:\n\t\tpanic(\"unexpected revision in put\")\n\tcase ret.sort != nil:","sourceCodeStart":275,"sourceCodeEnd":311,"githubUrl":"https://github.com/etcd-io/etcd/blob/f744d457f484e9f748a0700b48ef96dcf792df33/client/v3/op.go#L275-L311","documentation":"OpDelete rejects WithFilterDelete and WithFilterPut. Those flags tell a range read to omit delete/put events (or KV entries) from results; a delete range produces no such event stream to filter. The validation switch sees ret.filterDelete || ret.filterPut set and panics with \"unexpected filter in delete\".","triggerScenarios":"clientv3.OpDelete(\"/k\", clientv3.WithFilterPut()); forwarding watch or Get options (used to strip event types) into an invalidation Delete; shared option structs covering reads, watches, and writes.","commonSituations":"Event-pipeline code that already filters puts on the watch side and reuses the option bundle when clearing state; cache invalidation helpers mixing watch options with delete calls.","solutions":["Remove WithFilterDelete/WithFilterPut from Delete calls; they only belong on Get and Watch.","If the goal was to delete only put events' keys, that filtering must happen client-side: Get the keys, then delete them."],"exampleFix":"// before\n_, err := cli.Delete(ctx, stateKey, clientv3.WithFilterPut()) // panics\n\n// after\n_, err := cli.Delete(ctx, stateKey)","handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":["WithFilterPut/WithFilterDelete shape Watch/Get output; they are meaningless on writes.","Keep watch-construction options (filters, prevKV, progressNotify) separate from mutation options.","If you need 'delete only keys whose last event was a put', select them via Get/watch first."],"tags":["go","etcd","clientv3","delete","watch-filter","panic","invalid-option"],"backgroundTag":null,"analyzedSha":"f744d457f484e9f748a0700b48ef96dcf792df33","analyzedAt":"2026-08-15T09:39:50.079Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}