Description
Route registration order in backend/app/main.py:
- Lines ~105–120: API routers registered (
auth_router, documents_router, chat_router, github_router, admin_router, workspaces_router, health_router — all under /api/v1)
- Lines ~132–138:
if os.path.exists(FRONTEND_BUILD_DIR): app.api_route("/{full_path:path}", methods=["GET", "HEAD"])
- Lines ~158–160:
app.include_router(profile_router)
FastAPI/Starlette matches routes in registration order. The route /{full_path:path} matches any URL path for GET/HEAD requests. profile_router is registered after it, so its routes are never reached via GET/HEAD — the catch-all returns the frontend HTML instead.
The condition if os.path.exists(FRONTEND_BUILD_DIR): means this bug only exists in production/Docker (where the frontend is built). In development with no frontend build, the catch-all is not registered and profile routes work fine.
Impact
- All profile endpoints (
GET /profile, GET /profile/settings, etc.) return frontend SPA HTML instead of actual JSON API responses in production.
- Development/production inconsistency: works locally, broken in Docker.
- Silent failure: no error logged; the SPA may handle the wrong response gracefully, masking the bug from developers.
Fix Required (~1 line moved + potential robust fix)
backend/app/main.py:
- Move
app.include_router(profile_router) to join the other API route includes (around lines 105–120), before the frontend catch-all.
- More robust: change the catch-all to only match paths that don't conflict with known API routes.
GSSoC '26
Description
Route registration order in
backend/app/main.py:auth_router,documents_router,chat_router,github_router,admin_router,workspaces_router,health_router— all under/api/v1)if os.path.exists(FRONTEND_BUILD_DIR): app.api_route("/{full_path:path}", methods=["GET", "HEAD"])app.include_router(profile_router)FastAPI/Starlette matches routes in registration order. The route
/{full_path:path}matches any URL path for GET/HEAD requests.profile_routeris registered after it, so its routes are never reached via GET/HEAD — the catch-all returns the frontend HTML instead.The condition
if os.path.exists(FRONTEND_BUILD_DIR):means this bug only exists in production/Docker (where the frontend is built). In development with no frontend build, the catch-all is not registered and profile routes work fine.Impact
GET /profile,GET /profile/settings, etc.) return frontend SPA HTML instead of actual JSON API responses in production.Fix Required (~1 line moved + potential robust fix)
backend/app/main.py:app.include_router(profile_router)to join the other API route includes (around lines 105–120), before the frontend catch-all.GSSoC '26