Summary
The host's HTTPException handler rebuilds the response from status_code and detail only. Any headers a route attached to the exception are discarded. Starlette and FastAPI both honour HTTPException(headers=...) by default; the host's override silently loses them.
Observed
simple_module_hosting/_error_handlers.py:48-52 (0.0.26):
async def http_exception_handler(request: Request, exc: HTTPException) -> Response:
if exc.status_code in _INERTIA_ERROR_STATUSES:
detail = str(exc.detail) if exc.detail else ""
return await render_error_page(request, exc.status_code, detail)
return JSONResponse(status_code=exc.status_code, content={"detail": exc.detail})
Reproduction with any route:
@router.get("/x")
async def x():
raise HTTPException(404, "nope", headers={"Cache-Control": "no-store", "Vary": "X-Tenant-ID"})
curl -i http://localhost:8000/api/<module>/x
HTTP/1.1 404 Not Found
content-type: application/json
... # neither Cache-Control nor Vary is present
Why it matters
A public, cacheable read API on a multi-tenant host has to mark its 404s Cache-Control: no-store and Vary: <tenant header>, otherwise a shared cache keyed on the URL serves one tenant's 404 for another tenant's existing record. The same mechanism is how 401 carries WWW-Authenticate and 429 carries Retry-After. None of those can be set through the exception today.
Workaround in the records module
antosubash/smpy_modules#37 installs a custom APIRoute class (PublicErrorRoute) on its public router that catches HTTPException before the host handler and renders the JSON body itself so the headers survive. Every module wanting a header on an error response has to repeat that.
Proposed fix
return JSONResponse(status_code=exc.status_code, content={"detail": exc.detail}, headers=exc.headers)
and pass exc.headers through render_error_page for the Inertia branch as well.
Summary
The host's
HTTPExceptionhandler rebuilds the response fromstatus_codeanddetailonly. Anyheadersa route attached to the exception are discarded. Starlette and FastAPI both honourHTTPException(headers=...)by default; the host's override silently loses them.Observed
simple_module_hosting/_error_handlers.py:48-52(0.0.26):Reproduction with any route:
Why it matters
A public, cacheable read API on a multi-tenant host has to mark its 404s
Cache-Control: no-storeandVary: <tenant header>, otherwise a shared cache keyed on the URL serves one tenant's 404 for another tenant's existing record. The same mechanism is how401carriesWWW-Authenticateand429carriesRetry-After. None of those can be set through the exception today.Workaround in the records module
antosubash/smpy_modules#37 installs a custom
APIRouteclass (PublicErrorRoute) on its public router that catchesHTTPExceptionbefore the host handler and renders the JSON body itself so the headers survive. Every module wanting a header on an error response has to repeat that.Proposed fix
and pass
exc.headersthroughrender_error_pagefor the Inertia branch as well.