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.
Editor's take
This one surfaces at the worst moments in small startups running monolithic Django or FastAPI apps: the platform team lead deploys a new EC2 AMI or Docker base image, the CWD shifts from /app to /app/src, and every relative path call to open('data/config.yaml') silently resolves to a path that never existed on the new host. Nobody catches it pre-deploy because local dev paths matched by accident. The first signal is a 500 cascade at the load balancer health check endpoint, usually at 2am during a production cutover with a hard SLA window. By then the on-call engineer is staring at a traceback with no context on what os.getcwd() was at the time the process started.
This error is a first-month signal when it involves a hardcoded Windows-style backslash path ('C:\Users\dev\data') committed to a codebase that runs on Linux CI. It becomes a mid-career insight when the fix is not just the path but introducing pathlib.Path(__file__).parent / 'data' / 'config.yaml' as a discipline across the whole codebase — recognizing that CWD is a process-level global that any subprocess, test runner, or gunicorn worker can shift unexpectedly. A senior engineer who has been burned once will immediately reach for Path(__file__) anchoring and PYTHONPATH auditing before touching the path string itself.
In the same incident you will almost always find PermissionError: [Errno 13] alongside it if the path does exist on some hosts but with wrong ownership — a common artifact of Docker COPY steps that drop files as root. Upstream you will frequently see a KeyError or AttributeError in the config-loading layer where the missing file causes a None return that propagates silently before the open() call. Downstream, IsADirectoryError: [Errno 21] appears when a directory was created where a file was expected, which happens when volume mounts in docker-compose.yml shadow the container's WORKDIR with an empty host directory.
By Bikram Nath · Curator · Updated May 2026
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.