Python pathlib mkdir exceptions (tested)

Path.mkdir() can raise four different exceptions, and exist_ok=True doesn't silence all of them. We measured each one on Python 3.13.5.

1. parents and exist_ok

from pathlib import Path
import shutil
shutil.rmtree("out", ignore_errors=True)
p = Path("out/2026/10/report")
for kwargs in [{}, {"parents": True}, {"parents": True}, {"parents": True, "exist_ok": True}]:
    try:
        p.mkdir(**kwargs)
        print(f"mkdir({kwargs}) -> created")
    except OSError as e:
        print(f"mkdir({kwargs}) -> {type(e).__name__}: {e}")

Output:

mkdir({}) -> FileNotFoundError: [Errno 2] No such file or directory: 'out/2026/10/report'
mkdir({'parents': True}) -> created
mkdir({'parents': True}) -> FileExistsError: [Errno 17] File exists: 'out/2026/10/report'
mkdir({'parents': True, 'exist_ok': True}) -> created

Measured on Python 3.13.5: without parents=True, a missing parent directory is a FileNotFoundError naming the full path, even though only the parents are missing. Without exist_ok=True, running the script a second time is a FileExistsError. mkdir(parents=True, exist_ok=True) is the "make sure it exists" call, like mkdir -p.

2. exist_ok=True doesn't cover an existing file

from pathlib import Path
Path("data.txt").write_text("x")
for kwargs in [{"exist_ok": True}, {"parents": True, "exist_ok": True}]:
    try:
        Path("data.txt").mkdir(**kwargs)
    except OSError as e:
        print(f"existing FILE, mkdir({kwargs}) -> {type(e).__name__}: {e}")
try:
    Path("data.txt/sub").mkdir(parents=True, exist_ok=True)
except OSError as e:
    print(f"file in the parent path -> {type(e).__name__}: {e}")

Output:

existing FILE, mkdir({'exist_ok': True}) -> FileExistsError: [Errno 17] File exists: 'data.txt'
existing FILE, mkdir({'parents': True, 'exist_ok': True}) -> FileExistsError: [Errno 17] File exists: 'data.txt'
file in the parent path -> NotADirectoryError: [Errno 20] Not a directory: 'data.txt/sub'

Measured: exist_ok only ignores an existing directory. If a regular file has that name, you still get FileExistsError, and a file somewhere in the parent path gives NotADirectoryError. Look for a file (often an empty one created by an earlier bug) with the directory's name. More in our FileExistsError guide.

3. No write permission on the parent

import os
from pathlib import Path
Path("locked").mkdir(exist_ok=True)
os.chmod("locked", 0o555)                     # read-only directory
try:
    Path("locked/new").mkdir(exist_ok=True)
except OSError as e:
    print(f"{type(e).__name__}: {e}")
finally:
    os.chmod("locked", 0o755)

Output:

PermissionError: [Errno 13] Permission denied: 'locked/new'

Measured: creating a directory needs write permission on its parent; exist_ok doesn't change that. Create it somewhere you own, or fix the parent's permissions. (This test ran as a normal user; root ignores the mode.)

Frequently asked questions

Why does pathlib mkdir raise FileNotFoundError?

A parent directory is missing. We measured Path('out/2026/10/report').mkdir() failing on Python 3.13.5 and succeeding with parents=True.

Why does mkdir(exist_ok=True) still raise FileExistsError?

A regular file has that name; exist_ok only ignores an existing directory. We measured it raising even with parents=True.

What does mkdir(parents=True, exist_ok=True) do?

Creates the directory and any missing parents, and does nothing if it already exists as a directory, like mkdir -p. We measured it succeeding on a second run.

Why does mkdir raise NotADirectoryError?

A file sits where a parent directory should be. We measured Path('data.txt/sub').mkdir(parents=True, exist_ok=True) raising errno 20.

Written with AI assistance. Measured on 2026-10-09 with Python 3.13.5 on Linux as a normal user: Path.mkdir with each parents/exist_ok combination, over an existing file and through a file in the path, and in a 0o555 directory. Every block was run verbatim and its output pasted from that run. PlainHeaders is our extension; everything else here is built-in Python.