{"id":"8c9dbbe5481f4392","repo":"redis/redis-py","slug":"xclaim-force-must-be-a-boolean","errorCode":null,"errorMessage":"XCLAIM force must be a boolean","messagePattern":"XCLAIM force must be a boolean","errorType":"validation","errorClass":"DataError","httpStatus":null,"severity":"error","filePath":"redis/commands/core.py","lineNumber":7319,"sourceCode":"        pieces: list[EncodableT] = [name, groupname, consumername, str(min_idle_time)]\n        pieces.extend(list(message_ids))\n\n        if idle is not None:\n            if not isinstance(idle, int):\n                raise DataError(\"XCLAIM idle must be an integer\")\n            pieces.extend((b\"IDLE\", str(idle)))\n        if time is not None:\n            if not isinstance(time, int):\n                raise DataError(\"XCLAIM time must be an integer\")\n            pieces.extend((b\"TIME\", str(time)))\n        if retrycount is not None:\n            if not isinstance(retrycount, int):\n                raise DataError(\"XCLAIM retrycount must be an integer\")\n            pieces.extend((b\"RETRYCOUNT\", str(retrycount)))\n\n        if force:\n            if not isinstance(force, bool):\n                raise DataError(\"XCLAIM force must be a boolean\")\n            pieces.append(b\"FORCE\")\n        if justid:\n            if not isinstance(justid, bool):\n                raise DataError(\"XCLAIM justid must be a boolean\")\n            pieces.append(b\"JUSTID\")\n            kwargs[\"parse_justid\"] = True\n        return self.execute_command(\"XCLAIM\", *pieces, **kwargs)\n\n    @overload\n    def xdel(self: SyncClientProtocol, name: KeyT, *ids: StreamIdT) -> int: ...\n\n    @overload\n    def xdel(\n        self: AsyncClientProtocol, name: KeyT, *ids: StreamIdT\n    ) -> Awaitable[int]: ...\n\n    def xdel(self, name: KeyT, *ids: StreamIdT) -> int | Awaitable[int]:\n        \"\"\"","sourceCodeStart":7301,"sourceCodeEnd":7337,"githubUrl":"https://github.com/redis/redis-py/blob/da03cdc7e8731092b13e395605c3c1fb2de25de1/redis/commands/core.py#L7301-L7337","documentation":"Raised by xclaim() when `force` is truthy but not a bool. IMPORTANT: the guard lives under `if force:` (core.py:7317), so falsy non-bools (0, '', None) are silently treated as False and never trip this - only truthy non-bools like 1, 'yes', 'true' raise.","triggerScenarios":"xclaim(..., force=1), =force='yes'), =force='true'). force=0/None/'' does NOT raise (treated as False). force=True is correct.","commonSituations":"Passing force from a config flag that is a string 'true'; an int 0/1 from a checkbox; a numpy bool_.","solutions":["Pass an actual Python bool: force=bool(flag).","For string flags, normalize: flag_str.lower() in {'1','true','yes'}.","Note the asymmetry: 0 slips through but 1 does not - always bool()-coerce to be safe."],"exampleFix":"# before\nr.xclaim('s','g','c', 0, ['1-0'], force=cfg['force'])  # 'true' -> fails\n# after\nr.xclaim('s','g','c', 0, ['1-0'], force=str(cfg['force']).lower() in {'1','true','yes'})","handlingStrategy":"type-guard","validationCode":"force = bool(force)\nr.xclaim(name, group, consumer, ms, ids, force=force)","typeGuard":"def as_bool(v) -> bool:\n    if isinstance(v, bool): return v\n    if isinstance(v, str): return v.strip().lower() in {'1','true','yes','on'}\n    return bool(v)","tryCatchPattern":"from redis.exceptions import DataError\ntry:\n    r.xclaim(name, group, consumer, ms, ids, force=force)\nexcept DataError:\n    r.xclaim(name, group, consumer, ms, ids, force=bool(force))","preventionTips":["Always bool()-coerce flag values from config/UI.","Remember the guard only fires for truthy non-bools."],"tags":["redis","streams","xclaim","type-guard","argument-error","truthy-guard"],"analyzedSha":"da03cdc7e8731092b13e395605c3c1fb2de25de1","analyzedAt":"2026-08-04T20:26:47.563Z","schemaVersion":2}