crawl4ai version
0.9.0 (Docker server). Also present on develop — see below.
Summary
The monitor dashboard (/dashboard) never connects. Its WebSocket to /monitor/ws fails immediately; the browser console loops WebSocket connection to 'ws://<host>/monitor/ws' failed / WebSocket closed, and the header stays on Reconnecting…. The server returns HTTP 500 on the WS upgrade — even on a correctly authenticated request.
Root cause
deploy/docker/server.py includes the monitor router with a router-level dependency:
app.include_router(monitor_router, dependencies=[Depends(token_dep)])
token_dep is HTTP-only — deploy/docker/auth.py:
def get_token_dependency(config: Dict):
def _principal(request: Request) -> Optional[Dict]:
return get_principal(request)
return _principal
Because the dependency is attached at the router level, it is applied to every route in the router, including the @router.websocket("/ws") endpoint. On a WebSocket scope FastAPI cannot inject request: Request, so _principal() is called with no arguments:
TypeError: get_token_dependency.<locals>._principal() missing 1 required positional argument: 'request'
...which surfaces as a 500 and the WS never upgrades. Note the WS handler itself declares no Depends and its own docstring says auth is handled by the middleware:
@router.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
# Auth is enforced by the AuthGateMiddleware (outermost ASGI layer) ...
# No bespoke check here.
await websocket.accept()
So the router-level token_dep is both broken for WS and redundant (authentication is already enforced by AuthGateMiddleware, which is also what populates request.state.principal that require_admin reads).
Traceback
File ".../fastapi/dependencies/utils.py", line 680, in solve_dependencies
File ".../starlette/concurrency.py", line 34, in run_in_threadpool
...
TypeError: get_token_dependency.<locals>._principal() missing 1 required positional argument: 'request'
Steps to reproduce
- Run the 0.9.0 Docker server with
CRAWL4AI_API_TOKEN set.
- Open
/dashboard (send the token however your deployment allows — e.g. reverse-proxy header injection).
- The dashboard shows Reconnecting…; the server logs the
TypeError above on each /monitor/ws attempt (HTTP 500).
(There is a second, independent bug on this same route: prometheus_fastapi_instrumentator crashing on FastAPI's _IncludedRouter — reported in #2025 — which must also be resolved for the WS to work. This issue is specifically about the token_dep-on-WS TypeError.)
Fix
Drop the router-level dependency (auth stays enforced by AuthGateMiddleware; the destructive /monitor/actions/* and /monitor/stats/reset routes keep their own Depends(require_admin)):
-app.include_router(monitor_router, dependencies=[Depends(token_dep)])
+app.include_router(monitor_router)
(Alternatively, make the auth dependency WebSocket-aware, e.g. accept websocket: WebSocket in addition to request: Request — but since the middleware already gates the route, removing the redundant dependency is simplest.)
Verified with the change: /monitor/ws upgrades (101 Switching Protocols) and streams the expected JSON (health, requests, browsers, timeline, janitor, errors, timestamp); an unauthenticated WS is still rejected by AuthGateMiddleware; the admin action routes still require an admin credential.
Still present on develop
develop has the same line — app.include_router(monitor_router, dependencies=[Depends(token_dep)]) (server.py, ~line 486) with _principal(request: Request) (auth.py, ~line 130) — so this is not fixed there.
crawl4ai version
0.9.0 (Docker server). Also present on
develop— see below.Summary
The monitor dashboard (
/dashboard) never connects. Its WebSocket to/monitor/wsfails immediately; the browser console loopsWebSocket connection to 'ws://<host>/monitor/ws' failed/WebSocket closed, and the header stays on Reconnecting…. The server returns HTTP 500 on the WS upgrade — even on a correctly authenticated request.Root cause
deploy/docker/server.pyincludes the monitor router with a router-level dependency:token_depis HTTP-only —deploy/docker/auth.py:Because the dependency is attached at the router level, it is applied to every route in the router, including the
@router.websocket("/ws")endpoint. On a WebSocket scope FastAPI cannot injectrequest: Request, so_principal()is called with no arguments:...which surfaces as a 500 and the WS never upgrades. Note the WS handler itself declares no
Dependsand its own docstring says auth is handled by the middleware:So the router-level
token_depis both broken for WS and redundant (authentication is already enforced byAuthGateMiddleware, which is also what populatesrequest.state.principalthatrequire_adminreads).Traceback
Steps to reproduce
CRAWL4AI_API_TOKENset./dashboard(send the token however your deployment allows — e.g. reverse-proxy header injection).TypeErrorabove on each/monitor/wsattempt (HTTP 500).(There is a second, independent bug on this same route:
prometheus_fastapi_instrumentatorcrashing on FastAPI's_IncludedRouter— reported in #2025 — which must also be resolved for the WS to work. This issue is specifically about thetoken_dep-on-WSTypeError.)Fix
Drop the router-level dependency (auth stays enforced by
AuthGateMiddleware; the destructive/monitor/actions/*and/monitor/stats/resetroutes keep their ownDepends(require_admin)):(Alternatively, make the auth dependency WebSocket-aware, e.g. accept
websocket: WebSocketin addition torequest: Request— but since the middleware already gates the route, removing the redundant dependency is simplest.)Verified with the change:
/monitor/wsupgrades (101 Switching Protocols) and streams the expected JSON (health,requests,browsers,timeline,janitor,errors,timestamp); an unauthenticated WS is still rejected byAuthGateMiddleware; the admin action routes still require an admin credential.Still present on
developdevelophas the same line —app.include_router(monitor_router, dependencies=[Depends(token_dep)])(server.py, ~line 486) with_principal(request: Request)(auth.py, ~line 130) — so this is not fixed there.