{"id":"0ff909dfedb09ef5","repo":"redis/redis-py","slug":"start-argument-is-not-set-when-end-is-specified","errorCode":null,"errorMessage":"start argument is not set, when end is specified","messagePattern":"start argument is not set, when end is specified","errorType":"validation","errorClass":"DataError","httpStatus":null,"severity":"error","filePath":"redis/commands/core.py","lineNumber":2791,"sourceCode":"    ) -> int | Awaitable[int]:\n        \"\"\"\n        Return the position of the first bit set to 1 or 0 in a string.\n        ``start`` and ``end`` defines search range. The range is interpreted\n        as a range of bytes and not a range of bits, so start=0 and end=2\n        means to look at the first three bytes.\n\n        For more information, see https://redis.io/commands/bitpos\n        \"\"\"\n        if bit not in (0, 1):\n            raise DataError(\"bit must be 0 or 1\")\n        params = [key, bit]\n\n        start is not None and params.append(start)\n\n        if start is not None and end is not None:\n            params.append(end)\n        elif start is None and end is not None:\n            raise DataError(\"start argument is not set, when end is specified\")\n\n        if mode is not None:\n            params.append(mode)\n        return self.execute_command(\"BITPOS\", *params, keys=[key])\n\n    @overload\n    def copy(\n        self: SyncClientProtocol,\n        source: str,\n        destination: str,\n        destination_db: str | None = None,\n        replace: bool = False,\n    ) -> bool: ...\n\n    @overload\n    def copy(\n        self: AsyncClientProtocol,\n        source: str,","sourceCodeStart":2773,"sourceCodeEnd":2809,"githubUrl":"https://github.com/redis/redis-py/blob/da03cdc7e8731092b13e395605c3c1fb2de25de1/redis/commands/core.py#L2773-L2809","documentation":"bitpos() interprets an optional search range as [start, end]; end is only meaningful with a start. The guard at core.py:2790 rejects an end without a start because Redis BITPOS positional args require start before end.","triggerScenarios":"r.bitpos('key', 1, end=10) with start left at its default None. The elif at core.py:2790 catches start is None and end is not None.","commonSituations":"Calling bitpos with keyword-only end expecting it to mean 'first 10 bytes'; refactoring that dropped the start argument; building params from a dict that only set end.","solutions":["Always provide start when you provide end, e.g. r.bitpos('key', 1, start=0, end=10).","If you want the whole string, omit both start and end.","When constructing from optional config, set start=0 as the default whenever end is provided."],"exampleFix":"# before\nr.bitpos('key', 1, end=10)\n\n# after\nr.bitpos('key', 1, start=0, end=10)","handlingStrategy":"validation","validationCode":"if end is not None and start is None:\n    start = 0\nr.bitpos('key', bit, start=start, end=end)","typeGuard":"def valid_bitpos_range(start, end) -> bool:\n    return not (end is not None and start is None)","tryCatchPattern":"from redis.exceptions import DataError\ntry:\n    r.bitpos('key', bit, start=start, end=end)\nexcept DataError as e:\n    if 'start argument is not set' in str(e):\n        r.bitpos('key', bit, start=0, end=end)\n    else:\n        raise","preventionTips":["Provide start whenever you provide end; think of the range as a mandatory pair.","Default start=0 in your own wrapper if callers may omit it."],"tags":["redis","bitpos","validation","range","parameter-conflict"],"analyzedSha":"da03cdc7e8731092b13e395605c3c1fb2de25de1","analyzedAt":"2026-08-04T20:26:47.563Z","schemaVersion":2}