PermissionError: [Errno 13] Permission denied
1 · The lesson
readWhat this error means
The path exists, but the OS refused the operation because your process does not have permission for it.PermissionError is the Python translation of C EACCES / EPERM (errno 13). It is a subclass of OSError, so except OSError catches it. The cause is always one of: file system permission bits, ownership, an exclusive lock held by another process, or a security policy (SELinux, Windows ACL, AV).
When you see it
Traceback (most recent call last):
File "save.py", line 3, in <module>
with open("/etc/hosts", "w") as f:
PermissionError: [Errno 13] Permission denied: '/etc/hosts'On Windows you often see this when a file is open elsewhere:
PermissionError: [Errno 13] Permission denied: 'report.xlsx'
Why it happens
Cause 1 — you don't own the path. Writing to /etc/, /usr/, C:\Windows\, or another user's home raises this.
Cause 2 — the file is locked. On Windows, a file open in Excel, Word, or even Explorer's preview pane cannot be overwritten. Other processes on macOS / Linux can also hold mandatory locks.
Cause 3 — you opened a directory as a file (open("logs/")). Some systems return IsADirectoryError; others return PermissionError.
Cause 4 — antivirus, EDR, or a sandbox silently denying access to common malware locations (Temp, Startup, Documents on macOS).
Cause 5 — the executable bit / read bit is off. chmod 000 file then open(file) gives this.
How to fix it
Option 1 — write to a path you own. Your home directory, a temp directory, or your project folder.
from pathlib import Path out = Path.home() / "report.txt" out.write_text("...")
For genuinely temporary work, use tempfile:
import tempfile from pathlib import Path with tempfile.NamedTemporaryFile("w", delete=False, suffix=".csv") as f: f.write("a,b\n1,2\n") print("wrote", f.name)
Option 2 — close the file in the other program. On Windows, the offender is almost always Excel holding .xlsx open. Quit it and retry. To pre-empt this in tools you ship, write to a .tmp then rename:
from pathlib import Path final = Path("report.xlsx") tmp = final.with_suffix(".xlsx.tmp") tmp.write_bytes(payload) tmp.replace(final) # atomic on the same volume
setup added so this can run · defines payload
# Lightweight mock for objects whose attributes/methods aren't critical class _AutoMock: def __init__(self, name='mock'): self._name = name def __getattr__(self, k): return _AutoMock(self._name + '.' + k) def __call__(self, *a, **kw): print('-> ' + self._name + '() called') return _AutoMock(self._name + '()') def __repr__(self): return '<mock ' + self._name + '>' def __str__(self): return '<mock ' + self._name + '>' def __bool__(self): return True def __iter__(self): return iter([]) def __len__(self): return 0 def __getitem__(self, k): return _AutoMock(self._name + '[...]') def __setitem__(self, k, v): pass def __enter__(self): return self def __exit__(self, *a): return False async def __aenter__(self): return self async def __aexit__(self, *a): return False def __add__(self, o): return self def __radd__(self, o): return self def __sub__(self, o): return self def __mul__(self, o): return self def __rmul__(self, o): return self def __truediv__(self, o): return self def __eq__(self, o): return isinstance(o, _AutoMock) def __hash__(self): return hash(self._name) def __lt__(self, o): return True def __le__(self, o): return True def __gt__(self, o): return False def __ge__(self, o): return False def __mro_entries__(self, bases): return (object,) payload = _AutoMock('payload')
Option 3 — check and fix the bits (POSIX).
ls -l data.txt chmod u+w data.txt
Option 4 — don't escalate to admin to "fix" it. If your code needs admin to write somewhere, that is a design bug. Pick a user-writable location.
When you'd actually see this in real code
- A scheduled script writes to
C:\ProgramData\MyApp\and works on the developer's machine (admin) but not on a user's (standard). - Saving a report while the user has the previous version open in Excel.
- Docker container writes to a bind-mounted directory owned by a different UID on the host.
- macOS app trying to read
~/Documentswithout the "Full Disk Access" entitlement.
Related errors
FileNotFoundError— the path doesn't exist at all. See error-filenotfounderror.IsADirectoryError— you tried to open a directory as a file.OSError: [Errno 30] Read-only file system— the volume itself is mounted read-only.
See Also
- All Python errors — the full index, by type and by when it happens.
- File I/O — safe write patterns.
- pathlib —
Path.home(),Path.replace(). - cheat-files — fast lookup.