We see this one constantly in remote support sessions. Someone's python -m uvicorn main:app --reload process is running, something's broken with paths or imports, and they have no idea what directory the process actually started from. The good news: uvicorn working directory recovery is possible in most cases, and the fix is usually quicker than people expect. The bad news: Windows stores no history of the original directory, so if you haven't logged it, you're reconstructing from clues.
TL;DR
For uvicorn working directory recovery on Windows, add print(os.getcwd()) to main.py and let --reload restart the process. If no os.chdir() calls exist in your code, the printed value equals the original launch directory. For deeper inspection, expose a diagnostics endpoint or check your IDE run config, shortcut Start In field, or Task Scheduler action.
Key Takeaways
- Windows keeps no history of the original working directory. Recovery means reading the current value and reasoning about whether it has changed.
- If your code never calls
os.chdir(), the current directory equals the one inherited at launch. That's your answer. - The fastest route to uvicorn working directory recovery is adding one print statement and saving the file so --reload does the work.
- Task Scheduler and Windows services often default to
C:\Windows\System32when no working directory is set explicitly. That's a very common cause of module-not-found errors. - Preventing this in future takes about two minutes: log
os.getcwd()at startup and set an explicitcd /din your launch batch file.
At a Glance
- Difficulty: Intermediate
- Time Required: 5 to 30 mins depending on method
What Causes Uvicorn Working Directory Recovery Problems?
The root of this problem is a Windows limitation that surprises a lot of developers: the operating system records the current working directory of a running process, but it keeps absolutely no record of what that directory was at the moment the process was created. There's no audit trail, no event log entry, nothing. The Windows GetCurrentDirectory API gives you the live value, and that's all you get.
So where does the confusion actually come from in practice? A few places.
The most common one: someone opens a terminal, navigates to a project folder, starts uvicorn, then opens a second terminal tab and forgets which directory the first session was in. Or they close the original terminal entirely and the process keeps running in the background. Now they need to know the CWD and there's no obvious way to ask.
The second most common cause is IDE run configurations. VS Code, PyCharm, and similar tools all have a working directory setting in their run/debug configs. If that field is blank or set to the wrong folder, uvicorn inherits a CWD that doesn't match the project root. The process starts, uvicorn tries to find main:app relative to that directory, and either fails immediately or silently uses the wrong paths for file operations. This is especially dodgy when multiple projects are open in the same IDE window.
Third: os.chdir() in your own code or a dependency. This is the nasty one. Python inherits the parent shell's directory at launch and keeps it until something calls os.chdir(). If your app or a library changes the working directory at runtime, then os.getcwd() no longer reflects the original launch directory. You'd need to search the codebase for chdir calls to understand what happened.
Fourth: Task Scheduler and Windows services. When you launch a Python process through Task Scheduler without setting the 'Start in' field, Windows defaults the working directory to C:\Windows\System32. Every relative path in your app then resolves against that system folder. Imports fail, file reads fail, and the error messages can be genuinely confusing because they reference paths that look almost right.
Fifth: shortcuts. A Windows shortcut has a 'Start in' field under Properties. If that field is empty or wrong, the process inherits the shortcut file's own directory, which is rarely your project root.
Understanding which of these applies to your situation is the first step in uvicorn working directory recovery. The solutions below go from fastest to most thorough.
Uvicorn Working Directory Recovery: Quick Fix
This works if the process is still running and you can edit the source code. It takes about five minutes.
Print the CWD via Hot Reload Easy
- Open main.py
Add these two lines near the very top, before your app definition:import osprint('Current working directory:', os.getcwd()) - Save the file
The--reloadflag watches for file changes and restarts the process automatically. You don't need to kill and relaunch anything manually. - Read the terminal output
Look for a line like:Current working directory: C:\Users\You\Projects\MyApp
That is the live CWD. If your code has never calledos.chdir(), this is also the original launch directory. - Check for chdir calls
In VS Code, press Ctrl+Shift+F and search forchdiracross the whole project. In a terminal, run:findstr /s /i chdir *.py
If nothing comes back, the printed directory is your original working directory. Job done.
One thing people miss: the --reload mechanism in uvicorn uses watchfiles (or watchgod on older versions) to detect changes. Sometimes on Windows, especially over network drives or with certain antivirus tools running, the reload can be slow or miss a save. If the print statement doesn't appear after saving, try touching the file again or restarting uvicorn manually this once.
Also, if you're running uvicorn inside a Docker container on Windows, the CWD inside the container is what matters, not the Windows host path. The two can look completely different. In that case, the print statement approach still works, but the path you see will be the container's filesystem path, not a Windows path.
More Uvicorn Working Directory Recovery Solutions
If you can't edit the code right now, or if the quick fix didn't give you a clear answer, these intermediate approaches use Windows configuration interfaces to reconstruct how the process was launched.
Check the Parent Shell and IDE Config Easy
- Check the terminal window
If the terminal that launched uvicorn is still open, runcdwith no arguments in Command Prompt, orpwdin PowerShell. This shows the shell's current directory. If you haven't navigated away since starting uvicorn, this is the launch directory. - Check VS Code run configuration
Open the Command Palette with Ctrl+Shift+P, type 'Open launch.json', and look for acwdfield in the relevant configuration. If it's absent, VS Code defaults to the workspace root folder, which is usually the project root. Check that against the os.getcwd() output. - Check PyCharm run configuration
Go to Run, Edit Configurations. Select the uvicorn run config and look at the 'Working directory' field. This is the directory PyCharm passes to the process at launch.
Inspect Shortcuts and Task Scheduler Medium
- Check a Windows shortcut
Right-click the shortcut that launches your uvicorn process. Choose Properties. Look at the 'Start in' field. Whatever path is there becomes the working directory for the process. If it's blank, the process inherits the directory of the shortcut file itself, which is usually not your project root. - Check Task Scheduler
Open Task Scheduler from the Start menu. Find the task that runs python or uvicorn. Click it, then go to the Actions tab. Select the action and click Edit. The 'Start in (optional)' field is the working directory. If it's empty, Windows defaults toC:\Windows\System32, which explains a lot of import failures. - Cross-reference with uvicorn logs
If uvicorn logged any absolute file paths at startup (for example, when it found or failed to find main.py), those paths tell you what directory it was searching. A log line referencingC:\Users\You\Projects\MyApp\main.pyconfirms the working directory wasC:\Users\You\Projects\MyApp.
C:\Windows\System32 and every relative path in your app is resolving there. Fix it by setting the Start in field to your project root and restarting the task.Just as a misconfigured network adapter can cause connectivity headaches similar to a Vodafone router not working situation where the fix is buried in a settings panel you wouldn't normally check, the Task Scheduler Start in field is exactly that kind of hidden gotcha. It looks optional. It isn't, really.
Advanced Uvicorn Working Directory Recovery Fixes
These approaches go deeper. Use them when the process is long-running, you can't edit source code, or you need to be certain about the working directory for a production service.
Expose a Diagnostics Endpoint Medium
- Add a protected route to your FastAPI or Starlette app
This is the cleanest approach for a running service you can't restart:
Save and let --reload restart. Then hitimport os from pathlib import Path from fastapi import FastAPI app = FastAPI() @app.get('/_diag/cwd') def get_cwd(): return { 'os_getcwd': os.getcwd(), 'pathlib_cwd': str(Path.cwd()) }http://localhost:8000/_diag/cwdin a browser. - Protect it in production
Add an API key check or IP restriction to this route before deploying. Exposing server paths publicly is a security issue. In dev, it's fine as-is. - Read the response
Bothos_getcwdandpathlib_cwdshould return the same path. If they differ, something unusual is happening with the Python environment. Theos.getcwd()value is the authoritative one.
Reason from File Paths and Error Behaviour Advanced
- Read the uvicorn startup output carefully
When uvicorn starts, it logs the app it's loading. If it prints something likeLoading module 'main' from C:\Users\You\Projects\MyApp, that directory is the CWD. If it fails withModuleNotFoundError: No module named 'main', the CWD is wrong and doesn't contain main.py. - Trace FileNotFoundError messages
AnyFileNotFoundErrorusing a relative path tells you what the CWD is expected to be. If your code doesopen('data/config.json')and gets a FileNotFoundError, Python was looking for{CWD}\data\config.json. If you know where config.json actually lives, you can work backwards to what the CWD must have been for the error to occur. - Understand the Windows API layer
At the OS level, the GetCurrentDirectory Windows API is what Python's os.getcwd() calls internally. There is genuinely no Windows API for querying the directory at process creation time. This isn't a Python limitation. It's a Windows design. Recovery always means reading the current value and reasoning about whether it has changed.
Here's the thing: if you're hitting this problem repeatedly, it's a sign that the project's launch setup isn't documented well enough. That's not a criticism. It happens on every team eventually. But the fix isn't just recovering the directory this time. It's making sure you never have to recover it again. See the prevention section below.
One edge case that catches people out: if your app uses a library that calls os.chdir() internally (some older test frameworks and certain file-processing libraries do this), the CWD can change without any obvious sign in your own code. The findstr /s /i chdir *.py search only covers your project files. To catch library calls, you'd need to monkeypatch os.chdir at startup to log a traceback whenever it's called. That's a proper debugging technique for the really stubborn cases.
And just as a keyboard shortcut failing at the OS level, like a left Ctrl key not working in Windows, can have causes buried several layers deep in hardware and driver config, a wrong working directory can have causes buried in service wrappers, virtual environment activations, or parent process inheritance chains that aren't obvious from the surface.
If your uvicorn process is running in a production environment and you need to confirm the working directory without disrupting live traffic, our remote support team can connect directly, inspect the process, and get you sorted without any downtime.
Get remote helpPreventing Uvicorn Working Directory Recovery Issues
Prevention here is genuinely easy. The problem is that nobody does it until they've been burned once.
The single most useful habit: log os.getcwd() at startup. Not to the console, but to a file. Add this near the top of main.py:
import os, logging
logging.basicConfig(filename='startup.log', level=logging.INFO)
logging.info('Working directory at startup: %s', os.getcwd())
That file persists across restarts. Next time you need to know the original directory, it's already recorded. Two minutes of work, saves an hour of head-scratching later.
Second: stop relying on the CWD for file paths in your application code. Use Path(__file__).parent instead. This gives you the directory of the source file itself, which is stable regardless of what directory the process was launched from. So instead of open('data/config.json'), write open(Path(__file__).parent / 'data' / 'config.json'). Your code then works correctly no matter where uvicorn was started from.
Third: write a proper launch batch file. Something like:
@echo off
cd /d C:\Users\You\Projects\MyApp
python -m uvicorn main:app --reload
The cd /d sets both the drive and directory explicitly. No ambiguity. Run this batch file from anywhere and uvicorn always starts from the right place. Store it in the project root and commit it to version control.
Fourth: if you're using Task Scheduler, always fill in the 'Start in' field. Always. It's optional in the UI but it's not optional in practice. Set it to your project root and you'll never hit the C:\Windows\System32 problem again.
Fifth: document the expected launch directory in your README. One line: 'Run uvicorn from the project root: C:\Projects\MyApp'. That's it. Saves the next developer (or future you) from having to figure it out from scratch.
One more thing that's easy to overlook: if your project uses environment variables for configuration, add MYAPP_ROOT=C:\Projects\MyApp to your .env file and reference that variable in code instead of relying on the CWD. It's a bit like how display signal problems, similar to an HDMI to USB-C not working scenario, are much easier to prevent by using the right cable from the start than to diagnose after the fact. Setting the root variable explicitly means you never have to reconstruct it.
Uvicorn Working Directory Recovery: Summary
Uvicorn working directory recovery on Windows comes down to one core fact: Windows stores no history of the original directory, so you're always reading the current value and reasoning about whether it's changed. If your code doesn't call os.chdir(), the current value is the original. The fastest recovery method is adding print(os.getcwd()) to main.py and letting --reload do the restart. For running services you can't touch, expose a diagnostics endpoint or inspect the Task Scheduler action and IDE run configuration. And going forward, log the working directory at startup, use Path(__file__).parent for file paths, and always set an explicit directory in your batch files and Task Scheduler tasks. Uvicorn working directory recovery shouldn't need to happen twice on the same project.
Quick Reference
- Fastest fix: Add
print(os.getcwd())to main.py, save, read the --reload output - No code access: Check IDE run config, shortcut Start In field, or Task Scheduler action
- Production service: Add a protected
/_diag/cwdendpoint returning os.getcwd() - If paths are wrong: Check for
os.chdir()calls in your code and dependencies - Prevent it: Log CWD at startup, use
Path(__file__).parent, set explicit Start In fields everywhere


