{"id":"d4abebb654b5e398","repo":"redis/redis-py","slug":"xackdel-requires-at-least-one-message-id","errorCode":null,"errorMessage":"XACKDEL requires at least one message ID","messagePattern":"XACKDEL requires at least one message ID","errorType":"validation","errorClass":"DataError","httpStatus":null,"severity":"error","filePath":"redis/commands/core.py","lineNumber":6929,"sourceCode":"        groupname: GroupT,\n        *ids: StreamIdT,\n        ref_policy: Literal[\"KEEPREF\", \"DELREF\", \"ACKED\"] = \"KEEPREF\",\n    ) -> Awaitable[int]: ...\n\n    def xackdel(\n        self,\n        name: KeyT,\n        groupname: GroupT,\n        *ids: StreamIdT,\n        ref_policy: Literal[\"KEEPREF\", \"DELREF\", \"ACKED\"] = \"KEEPREF\",\n    ) -> int | Awaitable[int]:\n        \"\"\"\n        Combines the functionality of XACK and XDEL. Acknowledges the specified\n        message IDs in the given consumer group and simultaneously attempts to\n        delete the corresponding entries from the stream.\n        \"\"\"\n        if not ids:\n            raise DataError(\"XACKDEL requires at least one message ID\")\n\n        if ref_policy not in {\"KEEPREF\", \"DELREF\", \"ACKED\"}:\n            raise DataError(\"XACKDEL ref_policy must be one of: KEEPREF, DELREF, ACKED\")\n\n        pieces = [name, groupname, ref_policy, \"IDS\", len(ids)]\n        pieces.extend(ids)\n        return self.execute_command(\"XACKDEL\", *pieces)\n\n    @overload\n    def xadd(\n        self: SyncClientProtocol,\n        name: KeyT,\n        fields: Dict[FieldT, EncodableT],\n        id: StreamIdT = \"*\",\n        maxlen: int | None = None,\n        approximate: bool = True,\n        nomkstream: bool = False,\n        minid: StreamIdT | None = None,","sourceCodeStart":6911,"sourceCodeEnd":6947,"githubUrl":"https://github.com/redis/redis-py/blob/da03cdc7e8731092b13e395605c3c1fb2de25de1/redis/commands/core.py#L6911-L6947","documentation":"Raised by xackdel() when no message IDs are passed. XACKDEL combines acknowledgement and deletion for specific stream entries, so at least one ID must be supplied via the variadic *ids parameter; an empty ID list would be a no-op and is rejected up front.","triggerScenarios":"Calling client.xackdel(name, groupname) with no positional IDs, e.g. spreading an empty list: client.xackdel(name, groupname, *[]).","commonSituations":"Processing a batch that happened to be empty; spreading a list of pending IDs without guarding for the empty case.","solutions":["Pass at least one message ID: client.xackdel(name, groupname, '1234-0').","Guard the caller so XACKDEL is skipped when the ID list is empty.","Validate len(ids) > 0 before invoking the command."],"exampleFix":"# before\nawait r.xackdel('mystream', 'grp')\n# after\nawait r.xackdel('mystream', 'grp', '1234-0', '1235-0')","handlingStrategy":"validation","validationCode":"def validate_xackdel_ids(ids):\n    if not ids:\n        raise ValueError('XACKDEL requires at least one message ID')\n    return True","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Guard callers: skip XACKDEL when the ID list is empty.","Check len(ids) > 0 before spreading a list into *ids.","Log/handle empty batches explicitly rather than calling the command."],"tags":["stream","xackdel","validation","dataerror"],"analyzedSha":"da03cdc7e8731092b13e395605c3c1fb2de25de1","analyzedAt":"2026-08-04T20:26:47.563Z","schemaVersion":2}