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 xnack() when `mode` is not one of SILENT, FAIL, FATAL. Case-SENSITIVE set membership at core.py:7682. mode is a required positional argument (no default).

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 da03cdc7e8)

Solutions

  1. Pass one of the exact uppercase tokens.
  2. Map/normalize: mode = str(raw).upper(); assert mode in {'SILENT','FAIL','FATAL'}.
  3. Remember the semantics: SILENT=consumer shutting down, FAIL=unable to process, FATAL=poison message.

Example fix

# before
r.xnack('s','g','fail', *ids)  # lowercase
# after
r.xnack('s','g','FAIL', *ids)
Defensive patterns

Strategy: validation

Validate before calling

VALID = {'SILENT','FAIL','FATAL'}
mode = str(raw).upper()
if mode not in VALID:
    raise ValueError(f'mode must be one of {VALID}')
r.xnack(name, group, mode, *ids)

Type guard

def is_valid_xnack_mode(v) -> bool:
    return v in {'SILENT','FAIL','FATAL'}

Try / catch

from redis.exceptions import DataError
try:
    r.xnack(name, group, mode, *ids)
except DataError:
    r.xnack(name, group, 'FAIL', *ids)

Prevention

When it happens

Trigger: xnack('s','g','NACK'), =None, ='fail' (lowercase), ='silent'. Only exact 'SILENT','FAIL','FATAL' pass.

Common situations: Passing lowercase from config/UI; reusing an XCLAIM/xpending vocabulary; forgetting mode is required and shifting ids into its slot; passing None because the caller expected a default.

Related errors


AI-assisted analysis of redis/redis-py@da03cdc7e8 (2026-08-04). Data as JSON: /data/errors/7f66035bab638a90.json. Report an issue: GitHub.