redis/redis-py · error · DataError

XCLAIM force must be a boolean

Error message

XCLAIM force must be a boolean

What it means

Raised by Redis.xclaim() when force is truthy but not a bool. The check runs only inside `if force:`, so falsy values (False, 0, None, '') skip it. Truthy non-bool values like 1, 'yes', or 'true' fail isinstance(bool). force creates a PEL entry even if the IDs are not already pending.

Solutions

  1. Pass a real bool: force=True or force=False.
  2. Coerce config: force = bool(str_val.lower() == 'true').
  3. Do not use 1/0 or strings for force.

Example fix

# before
r.xclaim('s','g','c', 0, ['1-0'], force='true')

# after
r.xclaim('s','g','c', 0, ['1-0'], force=True)
Defensive patterns

Strategy: type-guard

Validate before calling

if not isinstance(force, bool):
    raise TypeError('force must be bool, got %r' % type(force))

Type guard

lambda v: isinstance(v, bool)

Try / catch

from redis.exceptions import DataError
try:
    r.xclaim('s','g','c', 0, ids, force=force)
except DataError as e:
    log.warning('bad xclaim force %r: %s', force, e)

Prevention

When it happens

Trigger: Calling r.xclaim(..., force=1), force='true', or force='yes'. force=True passes; force=0/False/None skip the check entirely.

Common situations: Passing a 'truthy' string from CLI/config to enable force, or using 1/0 instead of True/False.

Related errors


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

Appendix: source

Thrown at redis/commands/core.py:7319

        pieces: list[EncodableT] = [name, groupname, consumername, str(min_idle_time)]
        pieces.extend(list(message_ids))

        if idle is not None:
            if not isinstance(idle, int):
                raise DataError("XCLAIM idle must be an integer")
            pieces.extend((b"IDLE", str(idle)))
        if time is not None:
            if not isinstance(time, int):
                raise DataError("XCLAIM time must be an integer")
            pieces.extend((b"TIME", str(time)))
        if retrycount is not None:
            if not isinstance(retrycount, int):
                raise DataError("XCLAIM retrycount must be an integer")
            pieces.extend((b"RETRYCOUNT", str(retrycount)))

        if force:
            if not isinstance(force, bool):
                raise DataError("XCLAIM force must be a boolean")
            pieces.append(b"FORCE")
        if justid:
            if not isinstance(justid, bool):
                raise DataError("XCLAIM justid must be a boolean")
            pieces.append(b"JUSTID")
            kwargs["parse_justid"] = True
        return self.execute_command("XCLAIM", *pieces, **kwargs)

    @overload
    def xdel(self: SyncClientProtocol, name: KeyT, *ids: StreamIdT) -> int: ...

    @overload
    def xdel(
        self: AsyncClientProtocol, name: KeyT, *ids: StreamIdT
    ) -> Awaitable[int]: ...

    def xdel(self, name: KeyT, *ids: StreamIdT) -> int | Awaitable[int]:
        """

View on GitHub (pinned to 6a6b581b48)