{"record":{"id":"659a0feb5945bccc","repo":"python/cpython","slug":"negative-seek-position-r","errorCode":null,"errorMessage":"negative seek position %r","messagePattern":"negative seek position %r","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"Lib/_pyio.py","lineNumber":986,"sourceCode":"                if pos > len(self._buffer):\n                    # Pad buffer to pos with null bytes.\n                    self._buffer.resize(pos)\n                self._buffer[pos:pos + n] = view\n                self._pos += n\n            return n\n\n    def seek(self, pos, whence=0):\n        if self.closed:\n            raise ValueError(\"seek on closed file\")\n        try:\n            pos_index = pos.__index__\n        except AttributeError:\n            raise TypeError(f\"{pos!r} is not an integer\")\n        else:\n            pos = pos_index()\n        if whence == 0:\n            if pos < 0:\n                raise ValueError(\"negative seek position %r\" % (pos,))\n            self._pos = pos\n        elif whence == 1:\n            with self._lock:\n                self._pos = max(0, self._pos + pos)\n        elif whence == 2:\n            with self._lock:\n                self._pos = max(0, len(self._buffer) + pos)\n        else:\n            raise ValueError(\"unsupported whence value\")\n        return self._pos\n\n    def tell(self):\n        if self.closed:\n            raise ValueError(\"tell on closed file\")\n        return self._pos\n\n    def peek(self, size=0):\n        if self.closed:","sourceCodeStart":968,"sourceCodeEnd":1004,"githubUrl":"https://github.com/python/cpython/blob/bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6/Lib/_pyio.py#L968-L1004","documentation":"BytesIO.seek (Lib/_pyio.py:986) raises ValueError('negative seek position') when whence is 0 (SEEK_SET, the default) and pos is negative. An absolute position cannot be below zero, so instead of clamping or wrapping around, BytesIO rejects it outright. Note the whence==1 and whence==2 branches clamp with max(0, ...) — only absolute seeks are strict.","triggerScenarios":"buf.seek(-4) or buf.seek(-4, io.SEEK_SET); passing a negative offset computed from a header that turns out larger than expected; reusing socket-style negative offsets (meaning 'relative to end') with the default whence.","commonSituations":"Mixing up whence conventions: expecting seek(-n) to be relative (it is absolute by default); unsigned-length underflow from struct unpacking; backtracking computed as start - consumed where consumed > start.","solutions":["For positions relative to the end, pass the whence explicitly: `buf.seek(-4, io.SEEK_END)`.","For relative moves use `buf.seek(-4, io.SEEK_CUR)`.","Validate computed absolute offsets: `pos = max(0, computed)` only if clamping is intended, otherwise treat negative values as a parsing error."],"exampleFix":"# before\nbuf.seek(-4)  # absolute negative -> ValueError\n\n# after\nbuf.seek(-4, io.SEEK_END)  # 4 bytes before end, as intended","handlingStrategy":"validation","validationCode":"import io\ndef seek_absolute(buf: io.BytesIO, pos: int):\n    if pos < 0:\n        raise ValueError(f'absolute seek position must be >= 0, got {pos}')\n    buf.seek(pos, io.SEEK_SET)","typeGuard":null,"tryCatchPattern":"try:\n    buf.seek(pos)\nexcept ValueError as e:\n    if 'negative seek position' in str(e):\n        buf.seek(0)  # or raise a parse error: negative absolute offset is a logic bug\n    else:\n        raise","preventionTips":["Remember default whence is SEEK_SET (absolute); use io.SEEK_END/io.SEEK_CUR for negative offsets.","Validate computed offsets (`start - consumed`) and treat negatives as upstream parse errors.","Use the io.SEEK_* constants so intent is explicit in code review."],"tags":["io","bytesio","seek","whence","valueerror"],"backgroundTag":null,"analyzedSha":"bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6","analyzedAt":"2026-08-14T22:01:13.976Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}