redis/redis-py · error · DataError

XADD idmp must be a tuple of (producer_id, idempotent_id)

Error message

XADD idmp must be a tuple of (producer_id, idempotent_id)

What it means

Raised by xadd() when the idmp argument is not a 2-element tuple. idmp must be exactly (producer_id, idempotent_id) so the client can emit 'IDMP <producer_id> <idempotent_id>' on the wire; lists, shorter/longer tuples, or wrong types are rejected. It is a DataError.

Solutions

  1. Pass idmp as a tuple of exactly two elements: (producer_id, idempotent_id), e.g. ('producer-1', b'msg-uuid').
  2. Ensure the second element is the bytes idempotent_id unique per message.

Example fix

# before
client.xadd('s', {'f': 'v'}, idmp=['p1', b'iid'])

# after
client.xadd('s', {'f': 'v'}, idmp=('p1', b'iid'))
Defensive patterns

Strategy: type-guard

Validate before calling

if not isinstance(idmp, tuple) or len(idmp) != 2:
    raise ValueError('idmp must be a 2-tuple (producer_id, idempotent_id)')
client.xadd(name, fields, idmp=idmp)

Type guard

from typing import Any

def is_valid_idmp(v: Any) -> bool:
    return isinstance(v, tuple) and len(v) == 2

# usage
if not is_valid_idmp(idmp):
    raise TypeError('idmp must be (producer_id, idempotent_id)')

Try / catch

from redis.exceptions import DataError
try:
    client.xadd(name, fields, idmp=idmp)
except DataError as e:
    if 'idmp must be a tuple' in str(e):
        client.xadd(name, fields, idmp=tuple(idmp))

Prevention

When it happens

Trigger: Calling client.xadd('s', fields, idmp=['p1', b'iid']) (list not tuple), idmp=('p1',) (length 1), or idmp='p1' (string).

Common situations: Using a list instead of a tuple, omitting the idempotent_id, or passing a pre-flattened value.

Related errors


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

Appendix: source

Thrown at redis/commands/core.py:7035

        if idmpauto is not None and idmp is not None:
            raise DataError("Only one of ```idmpauto``` or ```idmp``` may be specified")

        if (idmpauto is not None or idmp is not None) and id != "*":
            raise DataError("IDMPAUTO and IDMP can only be used with id='*'")

        if ref_policy is not None and ref_policy not in {"KEEPREF", "DELREF", "ACKED"}:
            raise DataError("XADD ref_policy must be one of: KEEPREF, DELREF, ACKED")

        if nomkstream:
            pieces.append(b"NOMKSTREAM")
        if ref_policy is not None:
            pieces.append(ref_policy)
        if idmpauto is not None:
            pieces.extend([b"IDMPAUTO", idmpauto])
        if idmp is not None:
            if not isinstance(idmp, tuple) or len(idmp) != 2:
                raise DataError(
                    "XADD idmp must be a tuple of (producer_id, idempotent_id)"
                )
            pieces.extend([b"IDMP", idmp[0], idmp[1]])
        if maxlen is not None:
            if not isinstance(maxlen, int) or maxlen < 0:
                raise DataError("XADD maxlen must be non-negative integer")
            pieces.append(b"MAXLEN")
            if approximate:
                pieces.append(b"~")
            pieces.append(str(maxlen))
        if minid is not None:
            pieces.append(b"MINID")
            if approximate:
                pieces.append(b"~")
            pieces.append(minid)
        if limit is not None:
            pieces.extend([b"LIMIT", limit])
        pieces.append(id)

View on GitHub (pinned to 6a6b581b48)