python/cpython · error · ValueError

seek on closed file

Error message

seek on closed file

What it means

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.

Source

Thrown at Lib/_pyio.py:977

            if self.closed:
                raise ValueError("write to closed file")

            n = view.nbytes  # Size of any bytes-like object
            if n == 0:
                return 0

            with self._lock:
                pos = self._pos
                if pos > len(self._buffer):
                    # Pad buffer to pos with null bytes.
                    self._buffer.resize(pos)
                self._buffer[pos:pos + n] = view
                self._pos += n
            return n

    def seek(self, pos, whence=0):
        if self.closed:
            raise ValueError("seek on closed file")
        try:
            pos_index = pos.__index__
        except AttributeError:
            raise TypeError(f"{pos!r} is not an integer")
        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")

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Call seek() while the stream is open; move close() after the final re-read.
  2. Drop the explicit close() — BytesIO needs no deterministic cleanup.
  3. For repeated passes over the data, either keep the bytes (`data = buf.getvalue()`) and slice them, or reopen a fresh BytesIO(data).

Example fix

# before
buf = io.BytesIO(payload)
parse(buf)
buf.close()
buf.seek(0)  # ValueError: seek on closed file

# after
buf = io.BytesIO(payload)
parse(buf)
buf.seek(0)   # rewind while open
parse2(buf)
buf.close()   # close last (or omit)
Defensive patterns

Strategy: validation

Validate before calling

def rewind(buf: io.BytesIO):
    if buf.closed:
        raise ValueError('BytesIO closed; seek before close')
    buf.seek(0)

Type guard

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

Try / catch

try:
    buf.seek(0)
except ValueError as e:
    if 'closed file' in str(e):
        buf = io.BytesIO(original_bytes)  # reopen from retained data
    else:
        raise

Prevention

When it happens

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

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

Related errors


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