{"id":"2d360aab3db0aae0","repo":"redis/redis-py","slug":"xpending-idle-must-be-a-integer-0","errorCode":null,"errorMessage":"XPENDING idle must be a integer >= 0","messagePattern":"XPENDING idle must be a integer >= 0","errorType":"validation","errorClass":"DataError","httpStatus":null,"severity":"error","filePath":"redis/commands/core.py","lineNumber":7784,"sourceCode":"        if {min, max, count} == {None}:\n            if idle is not None or consumername is not None:\n                raise DataError(\n                    \"if XPENDING is provided with idle time\"\n                    \" or consumername, it must be provided\"\n                    \" with min, max and count parameters\"\n                )\n            return self.xpending(name, groupname)\n\n        pieces = [name, groupname]\n        if min is None or max is None or count is None:\n            raise DataError(\n                \"XPENDING must be provided with min, max \"\n                \"and count parameters, or none of them.\"\n            )\n        # idle\n        try:\n            if int(idle) < 0:\n                raise DataError(\"XPENDING idle must be a integer >= 0\")\n            pieces.extend([\"IDLE\", idle])\n        except TypeError:\n            pass\n        # count\n        try:\n            if int(count) < 0:\n                raise DataError(\"XPENDING count must be a integer >= 0\")\n            pieces.extend([min, max, count])\n        except TypeError:\n            pass\n        # consumername\n        if consumername:\n            pieces.append(consumername)\n\n        return self.execute_command(\"XPENDING\", *pieces, parse_detail=True)\n\n    @overload\n    def xrange(","sourceCodeStart":7766,"sourceCodeEnd":7802,"githubUrl":"https://github.com/redis/redis-py/blob/da03cdc7e8731092b13e395605c3c1fb2de25de1/redis/commands/core.py#L7766-L7802","documentation":"Raised by xpending_range() when idle, after int() coercion, is negative. Same try/except TypeError pattern as xautoclaim: non-coercible idle values are SWALLOWED and forwarded to Redis - only a successfully-parsed negative trips this. idle is only meaningful together with min/max/count (it lives after the all-None guard).","triggerScenarios":"r.xpending_range('s','g','-','+',10, idle=-1) or idle='-1'. idle=None or ='abc' does NOT raise here. Check at core.py:7782-7787.","commonSituations":"Computing an idle threshold from a delta that went negative due to clock skew; using -1 as 'no idle filter' (the correct way to omit is idle=None).","solutions":["Pass idle=None (or omit) to skip the idle filter.","Pass a non-negative int ms; clamp with max(0, int(raw)).","Pre-validate the type - the library's try/except silently passes non-int-coercible values."],"exampleFix":"# before\nr.xpending_range('s','g','-','+',10, idle=threshold)  # threshold < 0\n# after\nr.xpending_range('s','g','-','+',10, idle=None if threshold is None else max(0, int(threshold)))","handlingStrategy":"validation","validationCode":"idle = None if idle is None else max(0, int(idle))\nr.xpending_range(name, group, min, max, count, idle=idle)","typeGuard":null,"tryCatchPattern":"from redis.exceptions import DataError\ntry:\n    r.xpending_range(name, group, min, max, count, idle=idle)\nexcept DataError:\n    r.xpending_range(name, group, min, max, count, idle=max(0, int(idle)))","preventionTips":["Use idle=None to disable the filter, never -1.","The guard does not type-check - validate/coerce yourself."],"tags":["redis","streams","xpending","validation","range-error","weak-guard"],"analyzedSha":"da03cdc7e8731092b13e395605c3c1fb2de25de1","analyzedAt":"2026-08-04T20:26:47.563Z","schemaVersion":2}