os.getcwd
The working directory is process-wide state: every relative path, in every thread, is resolved against it. getcwd, getcwdb and chdir work everywhere; fchdir is Unix only and chroot is Unix only (not WASI, not Android). Prefer contextlib.chdir (Python 3.11+), which restores the old directory when the block ends.
Common call
with contextlib.chdir(path): ...
Returns
getcwd() → absolute str path
Replaces
Shelling out to pwd / cd
Watch out
chdir affects the whole process and all threads
os.getcwd() / os.getcwdb() / os.chdir(path) / os.fchdir(fd) / os.chroot(path)
→ str | bytes | None
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| path | str | bytes | PathLike | int | yes | chdir / chroot: the new directory. chdir also accepts an open directory file descriptor where os.supports_fd contains it (Unix). |
| fd | int | yes | fchdir: a file descriptor of an open directory (not a regular file). Equivalent to os.chdir(fd). |
Return value
str | bytes | None — getcwd returns an absolute str path, getcwdb the same as bytes; chdir, fchdir and chroot return None.
Common patterns
Temporarily work in another directory
contextlib.chdir (3.11+) restores the previous directory even when the block raises.
import contextlib import subprocess with contextlib.chdir('frontend'): subprocess.run(['npm', 'run', 'build'], check=True)
Run relative to the script, not the caller
Better than chdir: build absolute paths from the script location.
import os here = os.path.dirname(os.path.abspath(__file__)) config = os.path.join(here, 'config.toml')
Before Python 3.11
The same restore logic by hand.
import os prev = os.getcwd() os.chdir('build') try: run_build() finally: os.chdir(prev)
Come back via a directory descriptor (Unix)
fchdir returns to a directory even if it was renamed meanwhile.
import os home_fd = os.open('.', os.O_RDONLY) try: os.chdir('/tmp') ... finally: os.fchdir(home_fd) os.close(home_fd)
Examples
1. Inside contextlib.chdir
import contextlib
import os
os.mkdir('sub')
with contextlib.chdir('sub'):
inside = os.path.basename(os.getcwd())
inside
Returns
'sub'2. Restored afterwards
import contextlib
import os
start = os.getcwd()
os.mkdir('sub')
with contextlib.chdir('sub'):
pass
os.getcwd() == start
Returns
True3. getcwd is always absolute
import os
(os.path.isabs(os.getcwd()), os.path.abspath('x') == os.path.join(os.getcwd(), 'x'))
Returns
(True, True)4. getcwdb returns bytes
import os
(type(os.getcwdb()).__name__, os.fsdecode(os.getcwdb()) == os.getcwd())
Returns
('bytes', True)5. chdir to a missing directory
import os
try:
os.chdir('missing')
except OSError as e:
result = (type(e).__name__, e.errno)
result
Returns
('FileNotFoundError', 2)6. chdir to a file
import os
open('notes.txt', 'w').close()
try:
os.chdir('notes.txt')
except OSError as e:
result = type(e).__name__
result
Returns
'NotADirectoryError'Pitfalls
1. chdir without restoring it
If the code after chdir raises, the process stays in the other directory and every later relative path is wrong.
bare chdir
import os start = os.getcwd() os.mkdir('build') def build(): os.chdir('build') raise RuntimeError('compile failed') try: build() except RuntimeError: pass moved = os.getcwd() != start os.chdir(start) moved
True
contextlib.chdir
import contextlib import os start = os.getcwd() os.mkdir('build') def build(): with contextlib.chdir('build'): raise RuntimeError('compile failed') try: build() except RuntimeError: pass os.getcwd() != start
False
2. Relative paths change meaning after chdir
A relative path is resolved at the moment it is used. Make it absolute before changing directory.
relative
import contextlib import os open('data.txt', 'w').close() os.mkdir('sub') p = 'data.txt' with contextlib.chdir('sub'): found = os.path.exists(p) found
False
abspath first
import contextlib import os open('data.txt', 'w').close() os.mkdir('sub') p = os.path.abspath('data.txt') with contextlib.chdir('sub'): found = os.path.exists(p) found
True
When to use
Use it
- Reporting where the program runs, resolving user-supplied relative paths
- Running a tool that must be started inside a given folder (or pass cwd= to subprocess.run instead)
Reach for something else
- Threaded or async code → pass absolute paths; chdir is global
- Starting a child in another folder → subprocess.run(..., cwd=folder)
- Sandboxing → chroot is not a security boundary on its own and needs root
Notes
CPython impl
Thin wrappers over getcwd() / chdir() / fchdir() / chroot() in Modules/posixmodule.c. contextlib.chdir (Lib/contextlib.py) stores os.getcwd() on enter and calls os.chdir back on exit
Availability
getcwd, getcwdb, chdir: Unix and Windows. fchdir: Unix. chroot: Unix, not WASI, not Android. chdir(fd) works only where os.chdir is in os.supports_fd (true on Linux, false on Windows)
chroot
Changes the root directory of the process; an unprivileged call fails with PermissionError (EPERM, verified on Linux)
getcwdb
Since 3.8 it uses UTF-8 on Windows instead of the ANSI code page and is no longer deprecated there
Errors
chdir raises FileNotFoundError, NotADirectoryError or PermissionError (all OSError)
FAQ
The current directory is different on every machine and every run, so its value is never shown. The examples change into folders they create (with contextlib.chdir, which changes back) and compare names and paths relative to them.