redis/redis-py · error · DataError

XNACK mode must be one of: SILENT, FAIL, FATAL

Error message

XNACK mode must be one of: SILENT, FAIL, FATAL

What it means

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.

Solutions

  1. Use one of the exact uppercase literals: 'SILENT', 'FAIL', 'FATAL'.
  2. Coerce config: mode = str(mode).upper(); validate membership.

Example fix

# before
r.xnack('mystream', 'grp', 'silent', '1-0')

# after
r.xnack('mystream', 'grp', 'SILENT', '1-0')
Defensive patterns

Strategy: validation

Validate before calling

MODES = {'SILENT', 'FAIL', 'FATAL'}
mode = str(mode).upper()
if mode not in MODES:
    raise ValueError('mode must be one of %s' % MODES)

Type guard

lambda m: m in {'SILENT', 'FAIL', 'FATAL'}

Try / catch

from redis.exceptions import DataError
try:
    r.xnack('mystream', 'grp', mode, *ids)
except DataError as e:
    log.warning('bad xnack mode %r: %s', mode, e)

Prevention

When it happens

Trigger: Calling r.xnack('mystream', 'grp', 'silent') (lowercase), mode='RETRY', or a typo like 'SILENT '.

Common situations: Lowercasing from config, guessing the mode name, or confusing with XDELEX policies.

Related errors


AI-assisted analysis of redis/redis-py@6a6b581b48 (2026-08-10). Data as JSON: /api/errors/7f66035bab638a90. Report an issue: GitHub.

Appendix: source

Thrown at redis/commands/core.py:7683

                SILENT: consumer shutting down; decrements delivery counter.
                FAIL: consumer unable to process; delivery counter unchanged.
                FATAL: invalid/malicious message; delivery counter set to max.
            *ids: one or more message IDs to NACK.
            retrycount: optional integer >= 0. Overrides the mode's implicit
                delivery counter adjustment with an exact value.
            force: if True, creates a new unowned PEL entry for any ID not
                already in the group's PEL.

        Returns:
            The number of messages successfully NACKed.

        For more information, see https://redis.io/commands/xnack
        """
        if not ids:
            raise DataError("XNACK requires at least one message ID")

        if mode not in {"SILENT", "FAIL", "FATAL"}:
            raise DataError("XNACK mode must be one of: SILENT, FAIL, FATAL")

        pieces: list = [name, groupname, mode, "IDS", len(ids)]
        pieces.extend(ids)

        if retrycount is not None:
            if retrycount < 0:
                raise DataError("XNACK retrycount must be >= 0")
            pieces.extend([b"RETRYCOUNT", retrycount])

        if force:
            pieces.append(b"FORCE")

        return self.execute_command("XNACK", *pieces)

    @overload
    def xpending(
        self: SyncClientProtocol, name: KeyT, groupname: GroupT
    ) -> dict[str, Any]: ...

View on GitHub (pinned to 6a6b581b48)