github →

New request

#2782: GET /api/session returns 200 for missing sessions but POST 404s — UI stuck with no self-heal path

We'll provision a sandbox, run an agent against the issue, and open a draft PR. You can pull the branch and iterate from there.

Issue
bughelp wantedsprint-candidate

Summary

GET /api/session?session_id=<missing> returns HTTP 200 (with a tolerant payload) while POST /api/session/draft and POST /api/chat/start return 404 "Session not found" for the same missing session. This inconsistency prevents the studio UI from self-healing after a session is deleted server-side — the browser polls GET, sees 200, and never triggers the localStorage-clear / redirect-to-root branch. The user is stuck on a dead session with no in-app recovery path.

Compounding it: _sessionIdFromLocation() in static/sessions.js reads the session id from the URL path (/session/<id>) and uses it ahead of localStorage['hermes-webui-session'] in static/boot.js:1605-1607. So clearing localStorage doesn't help either — the URL keeps re-injecting the dead id on every reload.

Repro

  1. Open studio, create a session, note the URL path (/session/abc123).
  2. Server-side, delete the session file (or rebuild the container so the in-container state dir resets):
    docker compose up -d --force-recreate hermes-webui
    
  3. Reload the studio tab on the same URL.
  4. Try to send a chat.

Expected: UI realises the session is gone and redirects to a working state (empty + button, or restore from _index.json).

Actual: UI shows "Error: Session not found" on every send. Network tab: GET /api/session?session_id=abc123 → 200; POST /api/session/draft and /api/chat/start → 404.

Server-side code

  • api/routes.py GET /api/session handler (around line 3695) — wraps get_session(sid) in a try/except but returns 200 with tolerant content rather than 404.
  • api/routes.py POST /api/session/draft (around line 4920) and /api/chat/start (around line 5312) — get_session() KeyError → 404 "Session not found".

Client-side code

  • static/boot.js:1605-1607const saved = urlSession || savedLocal; URL wins over localStorage unconditionally.
  • static/sessions.js:568 — there's a self-heal branch that does localStorage.removeItem('hermes-webui-session') on a 404, but it's reached only under narrow conditions and not when the URL forces the bad id.

Suggested fix

Either of the following would close the loop. Both would be ideal:

  1. Server: make GET /api/session return 404 (consistent with POST) when the session_id is unknown. This lets the existing client-side self-heal branch trigger on the next poll.
  2. Client: when ANY session-scoped endpoint returns 404, strip /session/<id> from the URL (history.replaceState), clear hermes-webui-session from localStorage, and reset to the empty state. This makes the UI resilient to server-side session resets regardless of where the bad id came from.

Environment

  • hermes-webui pulled from ghcr.io/nesquena/hermes-webui@sha256:d568b23f97243f11a155d26a5757584751306d72be0c04ecfbac07417a795794 (currently running on my docker-host).
  • Triggered after I rebuilt the container during a multi-phase Hermes infra migration on 2026-05-22. Tab was still open on a /session/<old-id> URL from before the rebuild.
  • Worked around by manually navigating to the bare root URL https://studio.faneite.me/ and starting a new chat from there.

Happy to test a fix on my setup if useful.

Assessmentadvisory
bug●● medium90% confidence

Inconsistent HTTP semantics between GET and POST session endpoints require coordinated fixes in API handlers and frontend session-resolution / boot logic.

Likely files
  • api/routes.py
  • api/session.py
  • static/boot.js
  • static/sessions.js
  • static/api.js
Create the request

This opens a fresh agent run and a draft PR for issue #2782.

Cancel