{"record":{"id":"69c08b7c0626fab5","repo":"NousResearch/hermes-agent","slug":"res-status-text","errorCode":null,"errorMessage":"${res.status}: ${text}","messagePattern":"\\$\\{res\\.status\\}: \\$\\{text\\}","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"web/src/lib/api.ts","lineNumber":180,"sourceCode":"    // no-store`` so a reload picks up the freshly-injected token. Trigger\n    // that reload once on the first stale-token 401 — gated mode is\n    // handled above, so reaching here in gated mode means a real\n    // middleware failure that should not reload-loop.\n    if (!window.__HERMES_AUTH_REQUIRED__ && !options?.allowUnauthorized) {\n      if (attemptDashboardTokenReloadOnce()) {\n        return new Promise<T>(() => {});\n      }\n    }\n  }\n  if (res.ok) {\n    // Clear the stale-token reload guard: a successful 2xx proves the\n    // current ``window.__HERMES_SESSION_TOKEN__`` is valid, so the next\n    // 401 — if any — should be allowed to trigger its own reload cycle.\n    clearDashboardTokenReloadAttempt();\n  }\n  if (!res.ok) {\n    const text = await res.text().catch(() => res.statusText);\n    throw new Error(`${res.status}: ${text}`);\n  }\n  return res.json();\n}\n\n/** Encode a plugin registry key for URL paths (preserves `/` segment separators). */\nfunction pluginPath(name: string): string {\n  return name.split(\"/\").map(encodeURIComponent).join(\"/\");\n}\n\n/**\n * Fetch a single-use ticket for a WebSocket upgrade in gated mode.\n *\n * The dashboard's gated-mode WS auth (``hermes_cli.web_server._ws_auth_ok``)\n * rejects the legacy ``?token=<_SESSION_TOKEN>`` path and only accepts\n * ``?ticket=<minted>`` consumed against the in-memory ticket store. Browsers\n * can't set ``Authorization`` on a WS upgrade, so this round-trip via the\n * authenticated REST endpoint is the bridge from cookie auth to WS auth.\n *","sourceCodeStart":162,"sourceCodeEnd":198,"githubUrl":"https://github.com/NousResearch/hermes-agent/blob/c896c09c42910c584c4c7d2325b58c14713ea42c/web/src/lib/api.ts#L162-L198","documentation":"This is the dashboard's generic REST failure: authedFetch throws `${status}: ${body-text}` whenever a /api/* request returns a non-OK status. The response body text (or statusText when the body is unreadable) is inlined so the caller sees the server's error detail. A preceding successful 2xx clears the stale-token reload guard, meaning repeated 401s from here indicate a genuinely invalid session.","triggerScenarios":"Any dashboard REST call returning 4xx/5xx: 401 when the session token/cookie is expired in gated mode, 404 when a plugin/session resource no longer exists, 400 on malformed payloads, 500 from an unhandled gateway exception. `res.ok` is false and the body is read via res.text().","commonSituations":"Dashboard left open past session expiry, gateway restarted (invalidating in-memory tokens), multiple dashboard tabs after logout/re-login, or backend routes that changed between frontend and gateway versions (stale built web assets).","solutions":["Read the numeric status from the start of the message: 401 → reload the page or re-authenticate so a fresh session token/cookie is issued; other statuses → address the server-side detail in the message body.","Verify the gateway process is still running and the dashboard was served by it (not a stale cached SPA pointing at an old port).","If the status is 404, confirm the API route exists in the running gateway version — rebuild/redeploy matching web assets."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"if (!window.__HERMES_SESSION_TOKEN__ && window.__HERMES_AUTH_REQUIRED__) {\n  location.reload() // re-bootstrap credentials before firing API calls\n}","typeGuard":null,"tryCatchPattern":"try {\n  return await authedFetch<T>(path, init)\n} catch (err) {\n  if (String(err).startsWith('401')) {\n    clearDashboardTokenReloadAttempt() // allow one reload cycle\n    location.reload()\n    return new Promise<T>(() => {}) // halt callers while reloading\n  }\n  throw err\n}","preventionTips":["Treat the leading status code as the dispatch key (401 re-auth, 404 not-found, 5xx retry/log).","Never build UI that swallows this error silently — surface the server text.","Keep gateway and web bundle versions in lockstep to avoid route-shape 404s."],"tags":["http","rest","auth","dashboard"],"backgroundTag":null,"analyzedSha":"c896c09c42910c584c4c7d2325b58c14713ea42c","analyzedAt":"2026-08-14T17:18:01.089Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}