Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for Bash & Python Automation By James Joyner IV · · 9 min read Last reviewed Jul 2026

Python Error Guide: 'FileNotFoundError: [Errno 2] No such file or directory' — Fix Paths

Quick answer

Fix 'FileNotFoundError: [Errno 2] No such file or directory' in Python: fix wrong working directories, relative paths, and subprocess executables not on PATH.

  • #python
  • #automation
  • #troubleshooting
  • #errors
Free toolkit

Stuck on this Bash & Python Automation error? Get the free incident triage checklist

A one-page PDF — the exact steps to isolate, fix, and verify a production error like this one. No spam, unsubscribe anytime.

Overview

FileNotFoundError: [Errno 2] No such file or directory is Python’s wrapper around the OS ENOENT error: a path you referenced does not exist as named. It comes from two very different places, and telling them apart is the whole diagnosis. From open(), it means the file (or a parent directory) isn’t there:

Traceback (most recent call last):
  File "report.py", line 9, in <module>
    with open("config/settings.yaml") as fh:
FileNotFoundError: [Errno 2] No such file or directory: 'config/settings.yaml'

From subprocess, the same exception means the executable wasn’t found on PATH — not that a data file is missing:

FileNotFoundError: [Errno 2] No such file or directory: 'kubectl'

The quoted name at the end tells you which resource ENOENT applies to.

Symptoms

  • open() raises FileNotFoundError for a path that “exists” — but the script was launched from a different working directory.
  • A relative path works when you run the script from its own folder but fails from cron, systemd, or CI.
  • subprocess.run([...]) raises FileNotFoundError for the command name even though the command runs fine in your shell.
  • Writing to a nested path fails because an intermediate directory doesn’t exist yet.
  • The path in the error contains an unexpanded ~ or $VAR, revealing an un-expanded home or environment reference.

Common Root Causes

  • Wrong current working directory — relative paths resolve against os.getcwd(), which under cron/systemd is usually / or the unit’s WorkingDirectory, not where the script lives.
  • Relative path assumptionsopen("config/x.yaml") assumes the caller’s CWD contains config/.
  • Missing parent directory — opening logs/2026/run.log for write fails if logs/2026/ doesn’t exist (open does not create parents).
  • Unexpanded ~ or environment variables — Python does not expand ~ or $HOME automatically; the literal string is used as a path.
  • subprocess executable not on PATH — the command isn’t installed, or PATH is minimal under cron/systemd, so the interpreter can’t find the binary.
  • cwd= in subprocess pointing at a nonexistent directory — passing a cwd that doesn’t exist raises FileNotFoundError for that directory.
  • Symlink pointing at a deleted target — a dangling symlink resolves to a missing file.
  • Typo or wrong extension.yml vs .yaml, or a case-sensitivity mismatch on Linux.

Diagnostic Workflow

First determine which flavor you have — data file vs missing executable — by reading the quoted name. For a data-file case, print what the process actually sees:

import os
print("cwd:", os.getcwd())
target = "config/settings.yaml"
print("abs :", os.path.abspath(target))
print("exists:", os.path.exists(target))
print("dir listing:", os.listdir(os.path.dirname(os.path.abspath(target)) or "."))

Confirm from the shell, and reproduce the cron working directory:

pwd
ls -l config/settings.yaml 2>&1
( cd / && python /opt/app/report.py )   # simulate running from a different CWD

For the subprocess flavor, check whether the executable is resolvable — from Python, not just your shell:

import shutil
print(shutil.which("kubectl"))   # None means it is not on PATH for this process
command -v kubectl        # your interactive shell
env -i PATH=/usr/bin:/bin python -c "import shutil;print(shutil.which('kubectl'))"  # minimal PATH

Reveal unexpanded home/variables in the path:

p = "~/config/settings.yaml"
import os
print(os.path.expanduser(p))                 # expands ~
print(os.path.expandvars("$HOME/x.yaml"))    # expands $HOME

Example Root Cause Analysis

A reporting script read a template with a relative path and ran fine by hand but failed under cron:

Traceback (most recent call last):
  File "/opt/report/render.py", line 14, in <module>
    template = open("templates/report.html").read()
FileNotFoundError: [Errno 2] No such file or directory: 'templates/report.html'

The developer ran it as cd /opt/report && python render.py, so templates/ was found relative to /opt/report. Cron ran it as python /opt/report/render.py with a working directory of /, so open("templates/report.html") resolved to /templates/report.html, which didn’t exist. The os.getcwd() check made it obvious:

# by hand:  cwd: /opt/report
# via cron: cwd: /

The robust fix anchors every path to the script’s own location instead of the fragile working directory:

from pathlib import Path

BASE = Path(__file__).resolve().parent          # directory containing render.py
template_path = BASE / "templates" / "report.html"

if not template_path.is_file():
    raise SystemExit(f"Template missing: {template_path}")

template = template_path.read_text()

Path(__file__).resolve().parent makes the script location-independent, so it works identically from any working directory — cron, systemd, CI, or a shell.

Prevention Best Practices

  • Anchor paths to the script, not the CWD — use Path(__file__).resolve().parent (or a configured base directory) so relative paths don’t depend on where the process was launched.
  • Set WorkingDirectory= / cwd — for systemd units set WorkingDirectory=, and for cron cd into the right directory first, so relative paths resolve as intended.
  • Create parents before writingPath(p).parent.mkdir(parents=True, exist_ok=True) before open(p, "w").
  • Expand ~ and variables explicitlyos.path.expanduser() / os.path.expandvars() or Path.expanduser(); Python won’t do it for you.
  • Preflight and message clearly — check path.is_file() and raise a descriptive error naming the resolved absolute path, so the fix is obvious.
  • For subprocess, verify the executable — use shutil.which("cmd") first, or pass an absolute path, and set an explicit PATH for cron/systemd jobs.
  • Prefer pathlib — it makes path composition explicit and cross-platform, reducing string-concatenation mistakes.

Quick Command Reference

import os, shutil
from pathlib import Path
os.getcwd()                         # where relative paths resolve
Path(__file__).resolve().parent     # script's own directory (anchor here)
Path(p).parent.mkdir(parents=True, exist_ok=True)  # create parents before write
os.path.expanduser("~/x")           # expand home
shutil.which("kubectl")             # is the executable on PATH? (None = no)
Path(p).is_file()                   # preflight existence check
pwd; ls -l PATH        # confirm from the shell
command -v CMD         # is the subprocess executable resolvable?

Conclusion

FileNotFoundError: [Errno 2] means a named path doesn’t exist — but which path matters. From open(), it’s a data file or a missing parent directory, and the usual culprit is a relative path resolving against an unexpected working directory under cron or systemd. From subprocess, the identical error means the executable isn’t on PATH. Diagnose by printing os.getcwd() and the resolved absolute path (or shutil.which() for commands), then make paths robust by anchoring them to Path(__file__).resolve().parent, creating parents before writing, and expanding ~/variables explicitly.

Free download · 368-page PDF

Fixed it? Get 500 Bash & Python Automation & DevOps AI prompts — free

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.

Did this fix your issue?

Free download · 368-page PDF

Get 500 Battle-Tested DevOps AI Prompts — Free

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.