OSError: [Errno 29] Illegal seek (tested fix)

OSError: [Errno 29] Illegal seek means you called seek() or tell() on something that can only be read forward: a pipe, a socket, or stdin fed by |. We reproduced it in Python 3.13 and tested the fixes.

1. Seeking on a pipe: which call gives which error

Pipes, sockets, terminals and stdin fed by | can only be read forward. What you get depends on the call:

import io, os, sys
print(sys.version.split()[0])
r, w = os.pipe()
os.write(w, b"hi"); os.close(w)
for label, fn in (("os.lseek", lambda: os.lseek(r, 0, 0)),
                  ("unbuffered seek", lambda: os.fdopen(r, "rb", buffering=0, closefd=False).seek(0)),
                  ("buffered seek", lambda: os.fdopen(r, "rb", closefd=False).seek(0)),
                  ("buffered tell", lambda: os.fdopen(r, "rb", closefd=False).tell())):
    try:
        fn(); print(label, "ok")
    except OSError as e:
        print(f"{label:16} {type(e).__name__}: {e}  isinstance OSError={isinstance(e, OSError)}")

Output:

3.13.5
os.lseek         OSError: [Errno 29] Illegal seek  isinstance OSError=True
unbuffered seek  OSError: [Errno 29] Illegal seek  isinstance OSError=True
buffered seek    UnsupportedOperation: File or stream is not seekable.  isinstance OSError=True
buffered tell    OSError: [Errno 29] Illegal seek  isinstance OSError=True

Raw os.lseek, unbuffered files and tell() give [Errno 29] Illegal seek; a buffered seek() gives io.UnsupportedOperation. Both are OSError subclasses, so except OSError catches both. The message text comes from the C library: on Alpine (musl) the same errno reads Invalid seek (we saw it on python:3.8-alpine and 3.11-alpine).

2. Check with seekable() first

import os
r, w = os.pipe()
os.write(w, b"hello\n"); os.close(w)
f = os.fdopen(r, "rb")
print(f.seekable())
try:
    f.seek(0)
except OSError as e:
    print(type(e).__name__, e.errno, e)
try:
    f.tell()
except OSError as e:
    print("tell:", type(e).__name__, e)
print(f.read())

Output:

False
UnsupportedOperation None File or stream is not seekable.
tell: OSError [Errno 29] Illegal seek
b'hello\n'

3. Piped stdin can't be rewound

import subprocess, sys
child = "import sys; data = sys.stdin.read(); sys.stdin.seek(0); print('ok')"
r = subprocess.run([sys.executable, "-c", child], input="a,b\n1,2\n", capture_output=True, text=True)
print(r.stderr.strip().splitlines()[-1])
child2 = "import sys; data = sys.stdin.read(); print('read twice:', repr(data), repr(sys.stdin.read()))"
r = subprocess.run([sys.executable, "-c", child2], input="a,b\n", capture_output=True, text=True)
print(r.stdout.strip())

Output:

io.UnsupportedOperation: underlying stream is not seekable
read twice: 'a,b\n' ''

A script that reads sys.stdin twice gets an empty string the second time, and seek(0) fails. Code that works with < file.csv (a real file) breaks with cat file.csv |.

4. Fix: read once into memory

import io, os
r, w = os.pipe()
os.write(w, b"name,age\nann,31\n"); os.close(w)
raw = os.fdopen(r, "rb")
buf = io.BytesIO(raw.read())        # read once into memory
print(buf.read(4)); buf.seek(0); print(buf.read(4))

Output:

b'name'
b'name'

Wrap the data in io.BytesIO (or io.StringIO for text) and you can seek as often as you like. For large streams, write to tempfile.TemporaryFile() instead, which is seekable. Related: OSError errno codes list and [Errno 22] Invalid argument.

Frequently asked questions

What causes [Errno 29] Illegal seek?

seek() or tell() on a pipe, socket, terminal or piped stdin. These streams have no position to move to.

Why do I get UnsupportedOperation: File or stream is not seekable instead?

Buffered file objects check seekable() first and raise io.UnsupportedOperation, an OSError subclass. tell() and os.lseek still give Errno 29.

How do I re-read piped input?

Read it once into io.BytesIO or io.StringIO, or into tempfile.TemporaryFile() for large data, then seek on that.

Written with AI assistance. Measured on 2026-10-22 on Debian with Python 3.13.5 (block 1 also on the official python:3.8-alpine and python:3.11-alpine images): os.lseek, unbuffered and buffered seek and tell on a pipe, piped stdin, seekable(), and io.BytesIO. Every block was run verbatim and its output pasted unedited. PlainHeaders is our extension; Python is free.