{"record":{"id":"8af507ff3418ab99","repo":"python/cpython","slug":"tell-on-closed-file","errorCode":null,"errorMessage":"tell on closed file","messagePattern":"tell on closed file","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"Lib/_pyio.py","lineNumber":1000,"sourceCode":"        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:\n            raise ValueError(\"peek on closed file\")\n        if size < 1:\n            return self._buffer[self._pos:self._pos + io.DEFAULT_BUFFER_SIZE]\n        return self._buffer[self._pos:self._pos + size]\n\n    def truncate(self, pos=None):\n        if self.closed:\n            raise ValueError(\"truncate on closed file\")\n\n        with self._lock:\n            if pos is None:\n                pos = self._pos\n            else:\n                try:","sourceCodeStart":982,"sourceCodeEnd":1018,"githubUrl":"https://github.com/python/cpython/blob/bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6/Lib/_pyio.py#L982-L1018","documentation":"BytesIO.tell (Lib/_pyio.py:1000) returns the current absolute position self._pos, but first checks the closed flag and raises ValueError('tell on closed file'). After close() the buffer contents are gone even though the position variable survives, so reporting a position for a dead stream is treated as an error.","triggerScenarios":"buf.tell() after buf.close(); logging or asserting the read offset after cleanup closed the buffer; calling tell() in a finally block that runs after an explicit close in the error path.","commonSituations":"Progress logging in parsers that close on EOF and then report bytes consumed; debug code calling tell() on streams managed by a context manager that already exited.","solutions":["Capture the position before closing: `pos = buf.tell()` then `buf.close()`.","Remove premature close() calls; BytesIO requires no deterministic cleanup.","In finally blocks, guard with `if not buf.closed: log(buf.tell())`."],"exampleFix":"# before\nbuf = io.BytesIO(data)\nconsume(buf)\nbuf.close()\nlog('consumed up to %d', buf.tell())  # ValueError\n\n# after\nbuf = io.BytesIO(data)\nconsume(buf)\nlog('consumed up to %d', buf.tell())  # capture while open\nbuf.close()  # close last (or omit)","handlingStrategy":"validation","validationCode":"def position_of(buf: io.BytesIO) -> int:\n    if buf.closed:\n        raise ValueError('BytesIO closed; call tell() before close')\n    return buf.tell()","typeGuard":"def is_open_bytesio(v) -> bool:\n    return isinstance(v, io.BytesIO) and not v.closed","tryCatchPattern":"try:\n    pos = buf.tell()\nexcept ValueError as e:\n    if 'closed file' in str(e):\n        pos = -1  # position unreportable after close\n    else:\n        raise","preventionTips":["Capture tell() before close() when logging consumed offsets.","Guard tell() in finally blocks with `if not buf.closed:`.","Avoid explicit close() on BytesIO; let it go out of scope."],"tags":["io","bytesio","tell","lifecycle","valueerror"],"backgroundTag":null,"analyzedSha":"bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6","analyzedAt":"2026-08-14T22:01:13.976Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}