OSError: [Errno 9] Bad file descriptor (tested)
OSError: [Errno 9] Bad file descriptor almost always means something closed a descriptor that something else still uses. We reproduced six causes. In one of them the second close doesn't fail at all: it closes a different file.
Errno 9 (EBADF) means the operating system was handed a file descriptor number that isn't open, or isn't open for that operation. Python only passes the error on. Each cause below was run on Python 3.13.5 on Linux.
1. Closing the same descriptor twice
import os
fd = os.open("data.txt", os.O_WRONLY | os.O_CREAT)
os.close(fd)
try:
os.close(fd)
except OSError as e:
print(repr(e))
Output:
OSError(9, 'Bad file descriptor')
Measured: the second os.close() raises OSError(9, 'Bad file descriptor'). If you got this error, it's actually the lucky outcome; see section 5 for when the second close succeeds.
2. Closing a descriptor under a file object
import os
f = open("data.txt", "w")
os.close(f.fileno()) # someone closed the fd under the file object
f.write("hello") # buffered: no error yet
try:
f.close() # flush hits the dead fd
except OSError as e:
print("close():", repr(e))
print("f.closed:", f.closed)
Output:
close(): OSError(9, 'Bad file descriptor')
f.closed: True
Measured: f.write() succeeds because it only fills Python's buffer. The error appears later, at f.close(), when the buffer is flushed to the dead descriptor, so the traceback points at the wrong line. The data is lost and f.closed is True anyway.
import os, gc
f = open("data.txt", "w")
os.close(f.fileno())
f.write("hello")
del f; gc.collect()
print("program continues")
Output:
Exception ignored in: <_io.TextIOWrapper name='data.txt' mode='w' encoding='UTF-8'>
Traceback (most recent call last):
File "/tmp/bf/b6.py", line 5, in <module>
del f; gc.collect()
OSError: [Errno 9] Bad file descriptor
program continues
Measured: if nobody calls close(), the same error is printed as Exception ignored in: <_io.TextIOWrapper ...> when the object is garbage-collected, and the program continues with the data lost. That's the usual source of a stray "Exception ignored ... Bad file descriptor" at exit.
3. Two file objects sharing one descriptor
import os
fd = os.open("data.txt", os.O_WRONLY | os.O_CREAT)
f1 = os.fdopen(fd, "w")
f2 = os.fdopen(fd, "w") # two file objects, one fd
f1.close()
try:
f2.write("x"); f2.close()
except OSError as e:
print("f2:", repr(e))
Output:
f2: OSError(9, 'Bad file descriptor')
Measured: os.fdopen() takes ownership: closing f1 closes the descriptor that f2 is still using. The same happens with open(fd), socket.socket(fileno=fd), or wrapping sys.stdout.fileno().
import os
fd = os.open("data.txt", os.O_WRONLY | os.O_CREAT | os.O_TRUNC)
f1 = os.fdopen(fd, "w", closefd=False) # borrow, don't own
f1.write("one\n"); f1.close()
f2 = os.fdopen(os.dup(fd), "w") # own a separate copy
f2.write("two\n"); f2.close()
os.close(fd) # the real owner closes once
print(open("data.txt").read(), end="")
Output:
one
two
Fix (measured): pass closefd=False when you're only borrowing a descriptor, or give each object its own copy with os.dup(fd). Exactly one owner should close each descriptor.
4. Wrong mode: writing to a read-only descriptor
import os
fd = os.open("data.txt", os.O_RDONLY)
try:
os.write(fd, b"hi")
except OSError as e:
print("write to read-only fd:", repr(e))
f = open("data.txt", "r")
try:
f.write("hi")
except Exception as e:
print("write to 'r' file:", repr(e))
Output:
write to read-only fd: OSError(9, 'Bad file descriptor')
write to 'r' file: UnsupportedOperation('not writable')
Measured: at the os level, writing to a descriptor opened O_RDONLY is also Errno 9, even though the descriptor is open. A normal file object catches it earlier with io.UnsupportedOperation: not writable. If you see Errno 9 from os.write/os.read on an open fd, check its flags.
5. A closed socket
import socket
a, b = socket.socketpair()
a.close()
print("fileno after close:", a.fileno())
try:
a.send(b"x")
except OSError as e:
print("send:", repr(e))
Output:
fileno after close: -1
send: OSError(9, 'Bad file descriptor')
Measured: after close(), fileno() returns -1 and send() raises Errno 9. Typical cause: one thread or callback closes a socket another is still using. Check sock.fileno() == -1 or use with socket... so the lifetime is obvious.
6. The dangerous case: the second close succeeds
import os
fd = os.open("data.txt", os.O_RDONLY)
os.close(fd)
log = open("log.txt", "w") # gets the freed number
print("old fd:", fd, "log fd:", log.fileno())
os.close(fd) # second close: no error!
try:
log.write("important\n"); log.close()
except OSError as e:
print("log:", repr(e))
print("log.txt size:", os.path.getsize("log.txt"))
Output:
old fd: 3 log fd: 3
log: OSError(9, 'Bad file descriptor')
log.txt size: 0
Measured: the OS reuses the lowest free descriptor number, so a new file got the number 3 that had just been freed. The stale second os.close(3) raised nothing and silently closed the log file instead. The error then surfaced in unrelated code, and log.txt ended up empty. In threaded code this is a race that only shows up sometimes. After closing a raw descriptor, set your variable to None (or -1) so it can't be closed twice.
Frequently asked questions
What does OSError: [Errno 9] Bad file descriptor mean in Python?
The OS got a file descriptor number that isn't open (or isn't open for that operation). We measured it from a double os.close, a descriptor closed under a file object, two fdopen objects sharing one fd, a read-only fd, and a closed socket.
Why does the error happen on close() instead of write()?
File objects buffer writes, so the dead descriptor is only touched when the buffer is flushed. We measured write() succeeding and close() raising Errno 9, with the data lost.
What is 'Exception ignored in TextIOWrapper ... Bad file descriptor'?
A file object whose descriptor was already closed was garbage-collected and failed to flush. We measured the message and the program continuing with the data lost.
How do I share a file descriptor between two file objects?
Use os.fdopen(fd, closefd=False) for borrowers, or os.dup(fd) to give each its own copy. We measured both writing correctly.
Can closing a file descriptor twice close another file?
Yes. We measured a freed fd number being reused by a new file, and the stale second close silently closing that file instead, which left it empty.
Written with AI assistance. Measured on 2026-10-09 with Python 3.13.5 on Linux: double os.close, a descriptor closed under a file object (with and without close()), two os.fdopen objects on one fd, closefd=False and os.dup, writing to an O_RDONLY descriptor, a closed socketpair, and fd-number reuse. Every block was run verbatim and its output pasted from that run. PlainHeaders is our extension; everything else here is built-in Python.