{"record":{"id":"94831e39f9dfa967","repo":"python/cpython","slug":"seek-on-closed-file","errorCode":null,"errorMessage":"seek on closed file","messagePattern":"seek on closed file","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"Lib/_pyio.py","lineNumber":977,"sourceCode":"            if self.closed:\n                raise ValueError(\"write to closed file\")\n\n            n = view.nbytes  # Size of any bytes-like object\n            if n == 0:\n                return 0\n\n            with self._lock:\n                pos = self._pos\n                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\")","sourceCodeStart":959,"sourceCodeEnd":995,"githubUrl":"https://github.com/python/cpython/blob/bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6/Lib/_pyio.py#L959-L995","documentation":"BytesIO.seek (Lib/_pyio.py:977) begins with a closed-stream check and raises ValueError('seek on closed file'). The position bookkeeping still exists after close(), but the data is gone, so seeking is rejected to prevent a successful-looking seek followed by an empty read.","triggerScenarios":"buf.seek(0) after buf.close(); rewinding a BytesIO to re-parse it after a cleanup path closed it; seek() issued by a retry loop after an earlier attempt closed the buffer.","commonSituations":"Parse-then-reparse flows (seek(0) before a second pass) where the first pass's error handling closed the buffer; BytesIO used as a scratch file in code ported from real-file logic with explicit close.","solutions":["Call seek() while the stream is open; move close() after the final re-read.","Drop the explicit close() — BytesIO needs no deterministic cleanup.","For repeated passes over the data, either keep the bytes (`data = buf.getvalue()`) and slice them, or reopen a fresh BytesIO(data)."],"exampleFix":"# before\nbuf = io.BytesIO(payload)\nparse(buf)\nbuf.close()\nbuf.seek(0)  # ValueError: seek on closed file\n\n# after\nbuf = io.BytesIO(payload)\nparse(buf)\nbuf.seek(0)   # rewind while open\nparse2(buf)\nbuf.close()   # close last (or omit)","handlingStrategy":"validation","validationCode":"def rewind(buf: io.BytesIO):\n    if buf.closed:\n        raise ValueError('BytesIO closed; seek before close')\n    buf.seek(0)","typeGuard":"def is_open_bytesio(v) -> bool:\n    return isinstance(v, io.BytesIO) and not v.closed","tryCatchPattern":"try:\n    buf.seek(0)\nexcept ValueError as e:\n    if 'closed file' in str(e):\n        buf = io.BytesIO(original_bytes)  # reopen from retained data\n    else:\n        raise","preventionTips":["Rewind (seek(0)) while the stream is open; close only after the last pass.","For multi-pass parsing, retain data = buf.getvalue() and slice bytes instead of re-seeking.","Do not close BytesIO in producer/first-pass code when consumers re-seek."],"tags":["io","bytesio","seek","lifecycle","valueerror"],"backgroundTag":null,"analyzedSha":"bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6","analyzedAt":"2026-08-14T22:01:13.976Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}