{"record":{"id":"9452234ae410de40","repo":"python/cpython","slug":"tell-returned-an-invalid-position","errorCode":null,"errorMessage":"tell() returned an invalid position","messagePattern":"tell\\(\\) returned an invalid position","errorType":"exception","errorClass":"OSError","httpStatus":null,"severity":"error","filePath":"Lib/_pyio.py","lineNumber":788,"sourceCode":"    does *not* provide implementations of read(), readinto() or\n    write().\n    \"\"\"\n\n    def __init__(self, raw):\n        self._raw = raw\n\n    ### Positioning ###\n\n    def seek(self, pos, whence=0):\n        new_position = self.raw.seek(pos, whence)\n        if new_position < 0:\n            raise OSError(\"seek() returned an invalid position\")\n        return new_position\n\n    def tell(self):\n        pos = self.raw.tell()\n        if pos < 0:\n            raise OSError(\"tell() returned an invalid position\")\n        return pos\n\n    def truncate(self, pos=None):\n        self._checkClosed()\n        self._checkWritable()\n\n        # Flush the stream.  We're mixing buffered I/O with lower-level I/O,\n        # and a flush may be necessary to synch both views of the current\n        # file state.\n        self.flush()\n\n        if pos is None:\n            pos = self.tell()\n        # XXX: Should seek() be used, instead of passing the position\n        # XXX  directly to truncate?\n        return self.raw.truncate(pos)\n\n    ### Flush and close ###","sourceCodeStart":770,"sourceCodeEnd":806,"githubUrl":"https://github.com/python/cpython/blob/bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6/Lib/_pyio.py#L770-L806","documentation":"BufferedRaw.tell (Lib/_pyio.py:788) forwards to the raw stream's tell() and rejects negative results with OSError, since a stream position is by definition non-negative. Like its seek() sibling, this error indicates the underlying raw object violates the file-object protocol, not that the caller passed bad input.","triggerScenarios":"A custom raw stream whose tell() returns -1 before any read/seek, or returns the result of a failed lseek; tell() returning a signed arithmetic result that underflowed; a mock raw object with an incorrect tell stub.","commonSituations":"Wrapping non-seekable or stateful transports (pipes, compressed chunk readers) in BufferedReader and calling tell(); C-extension streams leaking -1 sentinels; test doubles returning -1 by copy-paste from C examples.","solutions":["Fix the custom tell() to always return a non-negative absolute offset (track position internally if the source has no tell).","Raise OSError in tell() when the underlying source genuinely cannot report position, and have callers check seekable()/handle the exception.","For non-seekable sources, prefer not implementing tell/seek at all and set seekable() to False."],"exampleFix":"# before\nclass MyRaw(io.RawIOBase):\n    def tell(self):\n        return -1  # before first read -> OSError\n\n# after\nclass MyRaw(io.RawIOBase):\n    def __init__(self):\n        self._off = 0\n    def tell(self):\n        return self._off  # maintained by read/seek","handlingStrategy":"validation","validationCode":"pos = f.tell() if f.seekable() else None  # honor capability before asking position\nif pos is None:\n    track_offset_manually = True","typeGuard":null,"tryCatchPattern":"try:\n    pos = f.tell()\nexcept OSError as e:\n    if 'invalid position' in str(e):\n        pos = None  # raw layer broken or untracked; degrade explicitly\n    else:\n        raise","preventionTips":["Custom tell() must return a non-negative absolute offset; maintain your own counter if the source lacks one.","Mark non-seekable sources with seekable() -> False and stop calling tell() on them.","Never copy C-style -1 sentinel returns into Python file-like adapters."],"tags":["io","tell","custom-stream","invariant","oserror"],"backgroundTag":null,"analyzedSha":"bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6","analyzedAt":"2026-08-14T22:01:13.976Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}