---
name: dashboard-auth-loopback-fix
title: Dashboard Auth Loopback Fix
---

# Bug Report: Dashboard infinite reload loop in loopback mode (no OAuth)

**Version:** v0.15.0 (v2026.5.28)

## Description

When running `hermes dashboard --host 0.0.0.0 --port 9119 --insecure --skip-build` (loopback / non-OAuth mode), the web dashboard enters an infinite full-page reload loop on every page load. The page reloads multiple times per second and cannot be interacted with.

## Root Cause

The SPA's `AuthWidget` component calls `GET /api/auth/me` on mount. In loopback mode there is no OAuth session, so `request.state.session` is never set. The endpoint raises `HTTPException(401)`:

```python
sess = getattr(request.state, "session", None)
if sess is None:
    raise HTTPException(status_code=401, detail="Unauthorized")
```

This 401 is caught by `fetchJSON()`'s global 401 handler in `web/src/lib/api.ts`, which was designed to handle stale session tokens after a server restart:

```typescript
if (!window.__HERMES_AUTH_REQUIRED__) {
    // stale-token recovery: set flag and reload once
    ...
    window.location.reload();
}
```

Since `/api/auth/me` returns 401 on **every** request in loopback mode, the first API call on every page load triggers `window.location.reload()`. The guard (`sessionStorage.getItem("hermes.tokenReloadAttempted")`) prevents a second reload on the same load, but the damage is done — the page never settles.

## Fix

### 1. `hermes_cli/dashboard_auth/routes.py`

Return a null envelope (200) instead of 401 when `session` is None:

```python
if sess is None:
    return {
        "user_id": None,
        "email": None,
        "display_name": None,
        "org_id": None,
        "provider": None,
        "expires_at": None,
    }
```

### 2. `hermes_cli/web_server.py`

Add `/api/auth/me` and `/api/auth/providers` to `_PUBLIC_API_PATHS`.

### 3. `web/src/components/AuthWidget.tsx`

Guard against null session data in loopback mode.

## Expected Behavior

In loopback mode, the AuthWidget should either not call `/api/auth/me`, or the endpoint should return a non-401 response that the AuthWidget can handle gracefully (render nothing).
