PermissionError

[Errno 13] Permission denied — the path exists, but the OS will not let this process do that to it. One class for two errnos: EACCES and EPERM.

InheritsBaseException›Exception›OSError›PermissionError
OS exceptionPython 3.3+Live demo
PermissionError(errnoerrno — errno.EACCES (permission bits, ACLs) or errno.EPERM (operation not allowed at all). Both map to this class.type: int · default: null, strerror[, filename])
Raised by
open(), os.remove(), os.mkdir(), shutil.*, os.kill()
Message
[Errno 13] Permission denied: 'path'
Quick fix
write somewhere you own (venv, home, temp dir)
Watch out
Windows: open() on a folder and files locked by other programs

Demo

Live evaluation
What the OS raises for a refused path: an OSError with errno EACCES, which the constructor turns into PermissionError.
Try:
Inputs
pathstr | Noneempty → None
Code
import errno
raise OSError(errno.EACCES, 'Permission denied', '/etc/shadow')
Uncaught exception
PermissionError: [Errno 13] Permission denied: '/etc/shadow'

Trigger raises OSError, yet the traceback says PermissionError — the constructor picked the subclass from errno.EACCES. Errno 13 is the same number on Linux, macOS and Windows. In Raise, a single argument is just the message: errno and strerror stay None, so code that inspects e.errno must not assume it is set.

Constructor

NameTypeRequiredDescription
errnointnoerrno.EACCES (permission bits, ACLs) or errno.EPERM (operation not allowed at all). Both map to this class.
strerrorstrno'Permission denied' (EACCES), 'Operation not permitted' (EPERM), 'Access is denied' from Windows API calls.
filenamestr | bytes | NonenoThe path the OS refused.

Attributes

AttributeTypeMeaning
errnoint | Noneerrno.EACCES or errno.EPERM when raised by the OS; None for the one-argument form.
strerrorstr | NoneThe OS text — "Permission denied", "Operation not permitted", or Windows "Access is denied".
filenamestr | bytes | NoneThe refused path.
winerrorintWindows only: 5 (access denied), 32 (file in use by another process), …

Common patterns

Fall back to a user-writable location
Try the preferred path; on PermissionError use one under the home directory.
from pathlib import Path
try:
    log = open('/var/log/myapp.log', 'a')
except PermissionError:
    log = open(Path.home() / 'myapp.log', 'a')
Tell the user what to do
Turn the low-level error into an actionable message.
try:
    shutil.copy(src, dst)
except PermissionError as e:
    raise SystemExit(f'cannot write {e.filename}: check ownership or pick another folder')
Portable "cannot open this path"
open() on a folder is IsADirectoryError on Linux/macOS but PermissionError on Windows — catch both.
try:
    text = open(path).read()
except (IsADirectoryError, PermissionError):
    text = None

Examples

1. EACCES and EPERM both map to it
import errno [type(OSError(c, 'x')).__name__ for c in (errno.EACCES, errno.EPERM)]
Returns
['PermissionError', 'PermissionError']
2. The OS-style message
import errno str(PermissionError(errno.EACCES, 'Permission denied', '/etc/shadow'))
Returns
"[Errno 13] Permission denied: '/etc/shadow'"
3. Tell EPERM from EACCES by errno
import errno e = OSError(errno.EPERM, 'Operation not permitted', 'app.pid') (type(e).__name__, e.errno == errno.EPERM)
Returns
('PermissionError', True)
4. Opening a folder, portably
import os os.mkdir('data') try: open('data') except (IsADirectoryError, PermissionError) as e: r = f'{e.filename} is not a readable file' r
Returns
'data is not a readable file'
5. Caught by except OSError
import errno try: raise OSError(errno.EACCES, 'Permission denied', 'db.sqlite') except OSError as e: r = (type(e).__name__, e.filename) r
Returns
('PermissionError', 'db.sqlite')
6. One-argument form
e = PermissionError('read-only mode') (e.args, e.errno)
Returns
(('read-only mode',), None)

Pitfalls

1. Checking only errno.EACCES
Some refusals come back as EPERM ("Operation not permitted"). Testing one errno misses the other; the class covers both.
e.errno == EACCES
import errno
try:
    raise OSError(errno.EPERM, 'Operation not permitted', 'app.pid')
except OSError as e:
    r = 'denied' if e.errno == errno.EACCES else 'other error'
r
'other error'
except PermissionError
import errno
try:
    raise OSError(errno.EPERM, 'Operation not permitted', 'app.pid')
except PermissionError:
    r = 'denied'
r
'denied'
2. except OSError turns "denied" into "missing"
A loader that treats every OSError as "no config file" silently ignores a config it is not allowed to read. Catch FileNotFoundError only and let PermissionError surface.
except OSError
import errno
def read_config(path):
    # the file exists but is not readable
    raise PermissionError(errno.EACCES, 'Permission denied', path)
try:
    cfg = read_config('app.ini')
except OSError:
    cfg = {}
cfg
{}
except FileNotFoundError
import errno
def read_config(path):
    # the file exists but is not readable
    raise PermissionError(errno.EACCES, 'Permission denied', path)
try:
    cfg = read_config('app.ini')
except FileNotFoundError:
    cfg = {}
cfg
PermissionError: [Errno 13] Permission denied: 'app.ini'

When to use

Use it
  • Falling back to a location the user owns
  • Turning a refused path into a clear, actionable message
  • Raising it from your own code for an access-control refusal
Reach for something else
  • Fixing it with sudo — install into a virtualenv or use --user instead
  • Checking os.access() first — the result can change before you open; just try
  • Treating it as "file missing" — that is FileNotFoundError

Notes

CPython impl
Objects/exceptions.c — EACCES, EPERM (and WASI ENOTCAPABLE) map to PermissionError
Catch via
except OSError
Windows
A file open in another program (Excel, an editor, antivirus) gives [WinError 32] … being used by another process; os.* calls say [WinError 5] Access is denied
root
Running as root (Docker images often do) bypasses file permission bits, so code that raises PermissionError on a laptop may not raise it in a container

FAQ

Find out which path it is (e.filename) and why: the file belongs to another user or is read-only; you are writing into a system folder (/usr, C:\Program Files); the path is actually a folder (Windows reports that as Permission denied); or, on Windows, another program has the file open. Fix the cause — write to a folder you own, close the other program, change ownership — rather than running Python as administrator.

History

3.3
Added, together with the other OSError subclasses (PEP 3151).
3.11.1
WASI's ENOTCAPABLE is now mapped to PermissionError.