{"record":{"id":"7f66035bab638a90","repo":"redis/redis-py","slug":"xnack-mode-must-be-one-of-silent-fail-fatal","errorCode":null,"errorMessage":"XNACK mode must be one of: SILENT, FAIL, FATAL","messagePattern":"XNACK mode must be one of: SILENT, FAIL, FATAL","errorType":"validation","errorClass":"DataError","httpStatus":null,"severity":"error","filePath":"redis/commands/core.py","lineNumber":7683,"sourceCode":"                SILENT: consumer shutting down; decrements delivery counter.\n                FAIL: consumer unable to process; delivery counter unchanged.\n                FATAL: invalid/malicious message; delivery counter set to max.\n            *ids: one or more message IDs to NACK.\n            retrycount: optional integer >= 0. Overrides the mode's implicit\n                delivery counter adjustment with an exact value.\n            force: if True, creates a new unowned PEL entry for any ID not\n                already in the group's PEL.\n\n        Returns:\n            The number of messages successfully NACKed.\n\n        For more information, see https://redis.io/commands/xnack\n        \"\"\"\n        if not ids:\n            raise DataError(\"XNACK requires at least one message ID\")\n\n        if mode not in {\"SILENT\", \"FAIL\", \"FATAL\"}:\n            raise DataError(\"XNACK mode must be one of: SILENT, FAIL, FATAL\")\n\n        pieces: list = [name, groupname, mode, \"IDS\", len(ids)]\n        pieces.extend(ids)\n\n        if retrycount is not None:\n            if retrycount < 0:\n                raise DataError(\"XNACK retrycount must be >= 0\")\n            pieces.extend([b\"RETRYCOUNT\", retrycount])\n\n        if force:\n            pieces.append(b\"FORCE\")\n\n        return self.execute_command(\"XNACK\", *pieces)\n\n    @overload\n    def xpending(\n        self: SyncClientProtocol, name: KeyT, groupname: GroupT\n    ) -> dict[str, Any]: ...","sourceCodeStart":7665,"sourceCodeEnd":7701,"githubUrl":"https://github.com/redis/redis-py/blob/6a6b581b48225afa0b76912d1028c6035baee932/redis/commands/core.py#L7665-L7701","documentation":"Raised by Redis.xnack() when mode is not one of 'SILENT', 'FAIL', 'FATAL'. These control delivery-counter behavior: SILENT decrements, FAIL leaves unchanged, FATAL sets to max. Case-sensitive uppercase.","triggerScenarios":"Calling r.xnack('mystream', 'grp', 'silent') (lowercase), mode='RETRY', or a typo like 'SILENT '.","commonSituations":"Lowercasing from config, guessing the mode name, or confusing with XDELEX policies.","solutions":["Use one of the exact uppercase literals: 'SILENT', 'FAIL', 'FATAL'.","Coerce config: mode = str(mode).upper(); validate membership."],"exampleFix":"# before\nr.xnack('mystream', 'grp', 'silent', '1-0')\n\n# after\nr.xnack('mystream', 'grp', 'SILENT', '1-0')","handlingStrategy":"validation","validationCode":"MODES = {'SILENT', 'FAIL', 'FATAL'}\nmode = str(mode).upper()\nif mode not in MODES:\n    raise ValueError('mode must be one of %s' % MODES)","typeGuard":"lambda m: m in {'SILENT', 'FAIL', 'FATAL'}","tryCatchPattern":"from redis.exceptions import DataError\ntry:\n    r.xnack('mystream', 'grp', mode, *ids)\nexcept DataError as e:\n    log.warning('bad xnack mode %r: %s', mode, e)","preventionTips":["Uppercase and validate mode at the config boundary; case-sensitive."],"tags":["stream","xnack","validation","enum"],"backgroundTag":null,"analyzedSha":"6a6b581b48225afa0b76912d1028c6035baee932","analyzedAt":"2026-08-10T12:52:44.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}