Troubleshooting¶
build:backend fails with ModuleNotFoundError: No module named 'fastapi'¶
PyInstaller is running against the wrong Python environment — almost
certainly anaconda's. Make sure backend-api/venv/ exists and was created
with:
bash
cd backend-api
python3 -m venv venv && source venv/bin/activate
pip install -r requirements.txt pyinstaller
scripts/build-backend.mjs auto-prefers backend-api/venv/bin/python when
present. If you see the warning "using bare pyinstaller from PATH" in
the build output, the venv wasn't found.
build:backend fails with No such file or directory: …/venv/bin/python3¶
The venv has stale shebangs (usually from being copied or its parent directory being renamed). Delete and recreate:
bash
cd backend-api && rm -rf venv && python3 -m venv venv && \
source venv/bin/activate && pip install -r requirements.txt pyinstaller
App launches but shows red "Backend failed to start" banner¶
In dev:
- If
backend-api/venvexists,npm run devruns uvicorn itself — check the[dev]lines in the terminal for a uvicorn startup failure. - Without a venv, the supervisor falls back to the bundled sidecar, so confirm
resources/phytograph_backend/exists (if not, runnpm run build:backend).
In packaged build: open macOS Console.app or Windows Event Viewer and
search for [Backend stderr]: lines from the supervisor.
Not every stderr line is an error
Python's logging writes to stderr by default, so INFO and WARNING
messages land there too. Read the message before assuming a failure.
Common causes:
- Quarantine bit on a fresh macOS install:
bash xattr -dr com.apple.quarantine /Applications/Phytograph.app - First-launch cold start (~30s with onedir, longer on slower disks)
- An orphaned backend from a previous session (it won't block the new
instance, which picks its own port, but it does consume memory):
bash pkill -f phytograph_backend # packaged bundle pkill -f 'uvicorn main:app' # dev
"Cannot remove quarantine" on macOS¶
The signed app's CodeSignature seal makes xattrs immutable. Either install via Finder drag (which clears quarantine on first launch via Gatekeeper approval), or use a signed+notarized CI build where this never comes up.
Renderer can't reach the backend¶
The renderer does not hardcode the port. initBackendUrl() in
src/renderer/utils/backendApi.ts fetches the real port from the main process
over the backend.getInfo IPC before the first render, and getBackendUrl()
returns that cached value. If requests are going to the wrong place, the
resolution step is what to inspect — don't edit getBackendUrl(). To pin a
specific port for both ends, set PHYTOGRAPH_BACKEND_PORT.
Plant generation / Helios features fail in dev only¶
PyHelios is built from the source submodule, not a pip wheel. Its native lib
(libhelios.dylib) lives at pyhelios/pyhelios_build/build/lib/ and must be
importable from the active Python env. If you replaced or recreated the venv,
or the submodule isn't initialized, rebuild from source:
bash
git submodule update --init --recursive # if pyhelios/ is empty
source backend-api/venv/bin/activate
node scripts/build-pyhelios.mjs # compiles libhelios + editable install
The backend also auto-rebuilds libhelios on startup when the C++ source is
newer than the compiled lib, so a stale lib usually fixes itself on the next
backend restart. A clean rebuild: node scripts/build-pyhelios.mjs --clean.
Stale backend processes¶
Since ports are resolved per instance, a leftover backend no longer blocks the
next launch — it lands on a different free port. (The supervisor only kills a
process on its own resolved port, and only when /version mismatches, so a
running dev backend is never killed by a test run or a second instance.)
To clean up orphans:
bash
pkill -f phytograph_backend # packaged bundle
pkill -f 'uvicorn main:app' # dev
To see what holds a specific port: lsof -ti :<port>.