PythonMastery
reference 3 min read · lesson 27 of 45 in Errors

PermissionError: [Errno 13] Permission denied

1 · The lesson

read

What 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

text
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:

text
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.

python
from pathlib import Path
out = Path.home() / "report.txt"
out.write_text("...")

For genuinely temporary work, use tempfile:

python
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:

python
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).

bash
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 ~/Documents without the "Full Disk Access" entitlement.
  • 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