Python FileNotFoundError: [Errno 2] No such file or directory
No such file or directory
Verified against Python docs: Built-in Exceptions, Python docs: pathlib, PEP 519 (PathLike protocol) · Updated May 2026
> quick_fix
Python tried to open a path that doesn't exist. Print the absolute path being opened and your current working directory. The fix is almost always: build the path from a known anchor like __file__ or pathlib.Path.cwd(), not a bare relative string.
from pathlib import Path
import sys
# Anchor to the file's directory, not CWD
HERE = Path(__file__).resolve().parent
data_path = HERE / 'data' / 'config.json'
print(f'Looking for: {data_path}')
print(f'Exists: {data_path.exists()}')
print(f'CWD: {Path.cwd()}', file=sys.stderr)What causes this error
open(), os.stat(), pathlib operations, and many third-party libraries raise FileNotFoundError (errno 2, ENOENT) when the path does not exist. The path is resolved relative to the process's current working directory unless absolute. CWD differs in cron, systemd, Docker, and IDE 'run' buttons. A path that works in dev fails in prod because CWD changes.
How to fix it
- 01
step 1
Print the absolute path the code is opening
Don't trust your mental model of relative paths. Print the resolved path right before opening. Compare it to ls output.
from pathlib import Path p = Path('config.json').resolve() print(f'Trying to open: {p}') print(f'Exists: {p.exists()}') print(f'CWD: {Path.cwd()}') - 02
step 2
Anchor relative paths to __file__, not CWD
A script run from /tmp opens config.json relative to /tmp. Anchor to the script's own directory so the path is stable regardless of where the user invoked python from.
# Bad - depends on CWD with open('data/config.json') as f: ... # Good - relative to the script HERE = Path(__file__).resolve().parent with open(HERE / 'data' / 'config.json') as f: ... # For packages, use importlib.resources from importlib.resources import files cfg = files('mypkg.data').joinpath('config.json') - 03
step 3
Create parent directories before writing
open(path, 'w') will fail if the parent directory does not exist. Create it first with parents=True, exist_ok=True so this is idempotent.
out = Path('logs') / 'app.log' out.parent.mkdir(parents=True, exist_ok=True) out.write_text('ok') - 04
step 4
Handle the case where the file is expected to be missing
If a missing file is a normal condition (cache miss, optional config), check first or catch the specific exception. Don't catch broad Exception.
try: cfg = json.loads(path.read_text()) except FileNotFoundError: cfg = {} # default # Or check first if path.is_file(): cfg = json.loads(path.read_text()) - 05
step 5
Watch for tilde expansion
open('~/.config/app.toml') does NOT expand ~ - that's a shell feature. Python sees a literal tilde-character path. Use Path.home() or Path.expanduser().
# Bad - literal tilde open('~/.config/app.toml') # FileNotFoundError # Good Path('~/.config/app.toml').expanduser().open() # Or (Path.home() / '.config/app.toml').open() - 06
step 6
In Docker, confirm WORKDIR and COPY paths
If your code expects ./data/, your Dockerfile must COPY data/ ./data/ AND set WORKDIR /app correctly. Common bug: WORKDIR is /app but data was COPY'd to /data.
# Dockerfile WORKDIR /app COPY src/ ./src/ COPY data/ ./data/ # CWD when CMD runs is /app, so 'data/x.json' resolves to /app/data/x.json
Why FileNotFoundError happens at the runtime level
Python's open() and pathlib internally call the OS open(2) syscall with the resolved path. When the kernel cannot find the inode (path component missing, dangling symlink, wrong CWD), it returns -1 with errno 2 (ENOENT). CPython's posixmodule.c maps errno values to subclasses of OSError; ENOENT maps to FileNotFoundError. The path printed in the exception's filename attribute is exactly what was passed to the syscall, after Python's own normalisation but before kernel-side resolution. Tilde, environment variables, and shell globs are NOT expanded by Python; they remain literal characters in the path string.
Common debug mistakes for FileNotFoundError
- Using a relative path in a cron job or systemd unit, where CWD is /root or / by default.
- Expecting tilde to be expanded in 'open("~/file")' - Python takes it literally.
- Reading a file with a path that has a trailing newline (from input() or readline()) without stripping it.
- Assuming COPY src/ /src/ in a Dockerfile keeps the same relative paths the host code uses.
- Catching FileNotFoundError but not logging the actual path; debugging requires knowing what was tried.
When FileNotFoundError signals a deeper problem
Recurring FileNotFoundError in production usually signals that the application has not separated configuration from filesystem layout. The architectural fix is to introduce a clear data-loading layer: a Config class that reads from Path(__file__).parent or an injected base directory, and importlib.resources for any data shipped inside a Python package. For multi-environment apps (dev, staging, prod), set a BASE_DIR via environment variable and resolve all paths against it. This decouples code from filesystem layout and lets Docker, CI, and dev environments all run the same code with different mount points.
Frequently asked questions
Why does my script work in PyCharm but fail when run from terminal?
PyCharm's default 'Run' configuration sets the working directory to the project root, so a path like data/config.json resolves correctly. When you run from terminal in a different directory, CWD is wherever you cd'd to. The fix is to never trust CWD: anchor every path to Path(__file__).resolve().parent or use importlib.resources for package data. This makes the script behave identically whether it's run from PyCharm, a cron job, a systemd unit, or a CI pipeline.
What is the difference between FileNotFoundError and IsADirectoryError?
FileNotFoundError (errno 2, ENOENT): the path doesn't exist at all. IsADirectoryError (errno 21, EISDIR): the path exists but it's a directory and you tried to open it as a file. PermissionError (errno 13, EACCES): the path exists, but you can't access it. NotADirectoryError (errno 20, ENOTDIR): a path component you treated as a directory is actually a file. All four inherit from OSError, so you can catch the parent if the distinction doesn't matter, but err.errno tells you which case fired.
How do I handle file paths cross-platform?
Always use pathlib.Path, never string concatenation with / or \. Path('a') / 'b' / 'c' produces a/b/c on Linux/macOS and a\b\c on Windows automatically. For paths users provide (like tilde-expanded home dirs), call .expanduser() to handle ~/. For URL-shaped paths in cross-platform code, .as_posix() forces forward slashes. The os.path module also works but its API is harder to chain and more error-prone.
Why does FileNotFoundError fire when I'm sure the file exists?
Three common reasons: (1) you printed the path and ls'd it from a different CWD; (2) the path includes a non-printing character (trailing newline from input(), invisible Unicode in copied paths); (3) symlink target is missing even though the symlink itself exists. Diagnose with Path(p).resolve().exists() and pathlib.Path(p).read_bytes() to see the raw bytes. Also confirm the user running Python has read permission with stat -c '%a' path on Linux.