Python OSError errno codes list (tested)

Every OSError carries the operating system's error number in e.errno. We measured the common codes on Python 3.13.5 and Linux, recorded which exception class Python raises for each, and linked our tested fix for each one.

1. The common codes, and the exception class Python raises for each

import errno, os
for n in (1, 2, 9, 13, 17, 20, 21, 22, 28, 32, 98, 104, 110, 111):
    try:
        raise OSError(n, os.strerror(n))
    except OSError as e:
        print(f"{n:>3} {errno.errorcode[n]:<13} {type(e).__name__:<23} {e.strerror}")

Output:

  1 EPERM         PermissionError         Operation not permitted
  2 ENOENT        FileNotFoundError       No such file or directory
  9 EBADF         OSError                 Bad file descriptor
 13 EACCES        PermissionError         Permission denied
 17 EEXIST        FileExistsError         File exists
 20 ENOTDIR       NotADirectoryError      Not a directory
 21 EISDIR        IsADirectoryError       Is a directory
 22 EINVAL        OSError                 Invalid argument
 28 ENOSPC        OSError                 No space left on device
 32 EPIPE         BrokenPipeError         Broken pipe
 98 EADDRINUSE    OSError                 Address already in use
104 ECONNRESET    ConnectionResetError    Connection reset by peer
110 ETIMEDOUT     TimeoutError            Connection timed out
111 ECONNREFUSED  ConnectionRefusedError  Connection refused

Measured on Python 3.13.5, Linux: constructing OSError(n, ...) with a known code automatically gives you the matching subclass: OSError(2, ...) is a FileNotFoundError. Codes with no dedicated subclass (EBADF 9, EINVAL 22, ENOSPC 28, EADDRINUSE 98) stay plain OSError, and for those you have to check e.errno. Both 1 and 13 map to PermissionError. The numbers are Linux's; other systems use different numbers for some codes, which is why you should compare against the errno.EXXX names, never the numbers.

2. What's on the exception

import errno
try:
    open("/nonexistent/config.json")
except OSError as e:
    print(type(e).__name__, "| errno:", e.errno, "| strerror:", e.strerror, "| filename:", e.filename)
    print(e.errno == errno.ENOENT, isinstance(e, FileNotFoundError), IOError is OSError)

Output:

FileNotFoundError | errno: 2 | strerror: No such file or directory | filename: /nonexistent/config.json
True True True

Measured: e.errno is the number, e.strerror the OS text, e.filename the path involved (often the fastest clue). IOError is just another name for OSError since Python 3.3 (IOError is OSError is True), so except IOError and except OSError catch exactly the same thing.

3. Handling: subclass first, errno for the rest

import errno, os
def safe_remove(path):
    try:
        os.remove(path)
    except FileNotFoundError:
        return "already gone"
    except OSError as e:
        if e.errno in (errno.EISDIR, errno.EPERM):   # Linux / macOS
            return f"is a directory ({errno.errorcode[e.errno]})"
        raise
    return "removed"
os.makedirs("d", exist_ok=True)
open("f.txt", "w").close()
print(safe_remove("f.txt"), "|", safe_remove("f.txt"), "|", safe_remove("d"))

Output:

removed | already gone | is a directory (EISDIR)

Measured: catch the specific subclass when one exists, check e.errno against errno names for the rest, and raise anything you didn't plan for. On Linux, os.remove() on a directory gave EISDIR; we've written the check to also accept EPERM, which other systems use for the same case (not measured here).

Measured fixes for each code

Frequently asked questions

What are Python OSError error codes?

e.errno holds the operating system's error number, e.g. 2 (ENOENT), 13 (EACCES), 17 (EEXIST). We measured 14 common codes on Linux and the exception class Python raises for each.

How do I get the errno from an OSError?

Use e.errno (number), e.strerror (text) and e.filename (path). Compare e.errno with names from the errno module, e.g. errno.ENOENT, rather than with numbers.

What is the difference between OSError and IOError?

There is none since Python 3.3: IOError is an alias. We measured IOError is OSError returning True.

Which errno values have their own exception class?

We measured FileNotFoundError (2), PermissionError (1, 13), FileExistsError (17), NotADirectoryError (20), IsADirectoryError (21), BrokenPipeError (32), ConnectionResetError (104), TimeoutError (110), ConnectionRefusedError (111). EBADF, EINVAL, ENOSPC and EADDRINUSE stay plain OSError.

Does OSError(2, ...) create a FileNotFoundError?

Yes. We measured OSError with a known errno automatically constructing the matching subclass.

Written with AI assistance. Measured on 2026-10-09 with Python 3.13.5 on Linux: 14 errno values mapped to their exception subclasses, the errno/strerror/filename attributes, IOError vs OSError, and a remove() handler that checks subclasses then errno. Every block was run verbatim and its output pasted from that run. PlainHeaders is our extension; everything else here is built-in Python.