python/cpython · error · ValueError

tell on closed file

Error message

tell on closed file

What it means

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.

Source

Thrown at Lib/_pyio.py:1000

        else:
            pos = pos_index()
        if whence == 0:
            if pos < 0:
                raise ValueError("negative seek position %r" % (pos,))
            self._pos = pos
        elif whence == 1:
            with self._lock:
                self._pos = max(0, self._pos + pos)
        elif whence == 2:
            with self._lock:
                self._pos = max(0, len(self._buffer) + pos)
        else:
            raise ValueError("unsupported whence value")
        return self._pos

    def tell(self):
        if self.closed:
            raise ValueError("tell on closed file")
        return self._pos

    def peek(self, size=0):
        if self.closed:
            raise ValueError("peek on closed file")
        if size < 1:
            return self._buffer[self._pos:self._pos + io.DEFAULT_BUFFER_SIZE]
        return self._buffer[self._pos:self._pos + size]

    def truncate(self, pos=None):
        if self.closed:
            raise ValueError("truncate on closed file")

        with self._lock:
            if pos is None:
                pos = self._pos
            else:
                try:

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Capture the position before closing: `pos = buf.tell()` then `buf.close()`.
  2. Remove premature close() calls; BytesIO requires no deterministic cleanup.
  3. In finally blocks, guard with `if not buf.closed: log(buf.tell())`.

Example fix

# before
buf = io.BytesIO(data)
consume(buf)
buf.close()
log('consumed up to %d', buf.tell())  # ValueError

# after
buf = io.BytesIO(data)
consume(buf)
log('consumed up to %d', buf.tell())  # capture while open
buf.close()  # close last (or omit)
Defensive patterns

Strategy: validation

Validate before calling

def position_of(buf: io.BytesIO) -> int:
    if buf.closed:
        raise ValueError('BytesIO closed; call tell() before close')
    return buf.tell()

Type guard

def is_open_bytesio(v) -> bool:
    return isinstance(v, io.BytesIO) and not v.closed

Try / catch

try:
    pos = buf.tell()
except ValueError as e:
    if 'closed file' in str(e):
        pos = -1  # position unreportable after close
    else:
        raise

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14). Data as JSON: /api/errors/8af507ff3418ab99. Report an issue: GitHub.