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.