The parent uvicorn process (python main.py) consumes ~100% CPU in production due to reload=True being hardcoded in main.py. This activates StatReload — a polling-based file watcher that runs rglob + pathlib.resolve + os.stat on
every .py file in the working tree every ~0.5 seconds.
Root cause confirmed via py-spy
All flamegraph samples point to a single hot path:
BaseReload.run
→ should_restart (statreload.py:32)
→ iter_py_files (statreload.py:52–53)
→ pathlib.Path.rglob
→ pathlib.Path.resolve / _joinrealpath / _select_from / __hash__
watchfiles is not installed in the container, so uvicorn falls back to StatReload (CPU polling) instead of the inotify-based WatchFilesReload. The production container has 1,657 .py files across 478 directories being stat'd in
a tight loop — 17× more than the local dev tree due to site-packages being within scan scope.
Fix
api/main.py, line 212 — use the existing DEVMODE env var (already defined in const.py):
- uvicorn.run("main:app", host=API_HOST, port=API_PORT, reload=True)
+ uvicorn.run("main:app", host=API_HOST, port=API_PORT, reload=DEVMODE)
DEVMODE is set to True only when DEVMODE=True is in the environment (see const.py:36), so it defaults to False in production with no .env changes needed.
run_api.sh also has --reload hardcoded — same fix:
-poetry run uvicorn main:app --host ${API_HOST} --port ${API_PORT} --reload
+poetry run uvicorn main:app --host ${API_HOST} --port ${API_PORT}
The parent uvicorn process (python main.py) consumes ~100% CPU in production due to reload=True being hardcoded in main.py. This activates StatReload — a polling-based file watcher that runs rglob + pathlib.resolve + os.stat on
every
.pyfile in the working tree every ~0.5 seconds.Root cause confirmed via py-spy
All flamegraph samples point to a single hot path:
watchfiles is not installed in the container, so uvicorn falls back to StatReload (CPU polling) instead of the inotify-based WatchFilesReload. The production container has 1,657
.pyfiles across 478 directories being stat'd ina tight loop — 17× more than the local dev tree due to site-packages being within scan scope.
Fix
api/main.py, line 212 — use the existing DEVMODE env var (already defined in const.py):
DEVMODE is set to True only when DEVMODE=True is in the environment (see const.py:36), so it defaults to False in production with no .env changes needed.
run_api.sh also has --reload hardcoded — same fix: