How can I specify working directory for a subprocess
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Pass the cwd parameter to subprocess.run(), subprocess.Popen(), or any of the other subprocess functions to set the child process's working directory. This changes where the child runs without altering the working directory of your main Python process. It is the correct approach in virtually every scenario where a subprocess needs to execute in a specific directory.
The command runs as if you had cd /tmp first, but your Python process stays in its original directory.
Why cwd Is Better Than os.chdir()
A common alternative is calling os.chdir() before launching the subprocess. This works, but it changes the working directory of the entire Python process, which affects all subsequent file operations, not just the subprocess you are about to launch.
Compare with cwd, which is scoped to the child process only:
| Approach | Affects parent process | Affects child process | Thread-safe |
cwd parameter | No | Yes | Yes |
os.chdir() | Yes (global) | Yes (inherited) | No (process-wide state) |
In multi-threaded applications, os.chdir() is particularly dangerous because changing the working directory affects every thread in the process simultaneously.
Using cwd with subprocess.run()
subprocess.run() is the recommended high-level function for most use cases. Here are practical examples across different operating systems.
Running a build command in a project directory
Running a git command in a specific repository
Windows example
Using cwd with subprocess.Popen()
For long-running processes or when you need streaming output, use Popen with the same cwd parameter:
Streaming output with error handling
Combining cwd with env
You can set both the working directory and environment variables for a subprocess. The env parameter replaces the entire environment, so you typically start with os.environ.copy() and add your overrides:
Using pathlib.Path for Cross-Platform Paths
Hard-coded string paths are brittle. pathlib.Path produces platform-appropriate paths and makes the code self-documenting:
subprocess.run() accepts both str and Path objects for cwd, so no explicit conversion is needed.
Handling Missing Directories
If the directory passed to cwd does not exist, Python raises FileNotFoundError before the subprocess is launched. The error message is clear but can be confused with the command itself not being found.
To guard against this, create the directory first:
Distinguishing directory errors from command errors
When cwd is invalid, the error looks like:
When the command itself is not found, the error looks like:
Both raise FileNotFoundError, but the message text differs. If you need programmatic distinction, check whether the path in the exception matches your cwd value:
Relative Paths Inside the Subprocess
Once cwd is set, all relative paths used by the subprocess resolve against that directory, not the parent Python process's directory. This is the entire point of cwd, but it still surprises people during debugging.
If the subprocess reads a config file with a relative path like config/settings.yml, it will look for /tmp/config/settings.yml in this example. Make sure the file exists relative to the cwd you specified, not relative to your script.
A Real-World Pattern: Running Tests in a Subdirectory
A common use case is running a test suite in a specific project directory:
Each subprocess.run() call operates in its own directory without interfering with the others or with the parent process.
Common Pitfalls
Using os.chdir() when only a subprocess needs a different directory. This changes the working directory for the entire process, affecting all subsequent file operations and any other threads. Use cwd instead.
Assuming relative paths in the subprocess resolve against the script's location. They resolve against the cwd you specified. If you write output to results/data.csv and cwd is /tmp, the file ends up at /tmp/results/data.csv.
Confusing "directory not found" with "command not found." Both raise FileNotFoundError. Check the exception message to determine which path is missing.
Not using check=True. Without check=True, a subprocess that fails (non-zero exit code) returns normally, and your code continues as if nothing went wrong. Unless you have a specific reason to handle failure manually, use check=True to raise CalledProcessError on failure.
Passing a file path instead of a directory path to cwd. The cwd parameter expects a directory. Passing a file path raises NotADirectoryError.
Using shell=True with cwd on Windows. When shell=True is set on Windows, the command runs through cmd.exe. The cwd parameter still works, but the behavior of relative paths can differ because cmd.exe has its own path resolution rules.
Summary
- Use the
cwdparameter onsubprocess.run()orsubprocess.Popen()to set the child process's working directory. cwdonly affects the child process. The parent Python process stays in its original directory.- Prefer
cwdoveros.chdir()becauseos.chdir()is global, affects all threads, and makes the program harder to reason about. pathlib.Pathobjects are accepted directly bycwd. No string conversion is needed.- If the directory does not exist, Python raises
FileNotFoundErrorbefore the subprocess starts. Create it withmkdir(parents=True, exist_ok=True)if needed. - All relative paths used by the subprocess resolve against the
cwddirectory, not the parent script's location. - Always use
check=Trueunless you have a specific reason to handle subprocess failure manually.

