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.

os functionPython 3 (all)
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

NameTypeRequiredDescription
pathstr | bytes | PathLike | intyeschdir / chroot: the new directory. chdir also accepts an open directory file descriptor where os.supports_fd contains it (Unix).
fdintyesfchdir: 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
True
3. 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.