NousResearch/hermes-agent · error

${res.status}: ${text}

Error message

${res.status}: ${text}

What it means

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.

Source

Thrown at web/src/lib/api.ts:180

    // no-store`` so a reload picks up the freshly-injected token. Trigger
    // that reload once on the first stale-token 401 — gated mode is
    // handled above, so reaching here in gated mode means a real
    // middleware failure that should not reload-loop.
    if (!window.__HERMES_AUTH_REQUIRED__ && !options?.allowUnauthorized) {
      if (attemptDashboardTokenReloadOnce()) {
        return new Promise<T>(() => {});
      }
    }
  }
  if (res.ok) {
    // Clear the stale-token reload guard: a successful 2xx proves the
    // current ``window.__HERMES_SESSION_TOKEN__`` is valid, so the next
    // 401 — if any — should be allowed to trigger its own reload cycle.
    clearDashboardTokenReloadAttempt();
  }
  if (!res.ok) {
    const text = await res.text().catch(() => res.statusText);
    throw new Error(`${res.status}: ${text}`);
  }
  return res.json();
}

/** Encode a plugin registry key for URL paths (preserves `/` segment separators). */
function pluginPath(name: string): string {
  return name.split("/").map(encodeURIComponent).join("/");
}

/**
 * Fetch a single-use ticket for a WebSocket upgrade in gated mode.
 *
 * The dashboard's gated-mode WS auth (``hermes_cli.web_server._ws_auth_ok``)
 * rejects the legacy ``?token=<_SESSION_TOKEN>`` path and only accepts
 * ``?ticket=<minted>`` consumed against the in-memory ticket store. Browsers
 * can't set ``Authorization`` on a WS upgrade, so this round-trip via the
 * authenticated REST endpoint is the bridge from cookie auth to WS auth.
 *

View on GitHub (pinned to c896c09c42)

Solutions

  1. 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.
  2. Verify the gateway process is still running and the dashboard was served by it (not a stale cached SPA pointing at an old port).
  3. If the status is 404, confirm the API route exists in the running gateway version — rebuild/redeploy matching web assets.
Defensive patterns

Strategy: retry

Validate before calling

if (!window.__HERMES_SESSION_TOKEN__ && window.__HERMES_AUTH_REQUIRED__) {
  location.reload() // re-bootstrap credentials before firing API calls
}

Try / catch

try {
  return await authedFetch<T>(path, init)
} catch (err) {
  if (String(err).startsWith('401')) {
    clearDashboardTokenReloadAttempt() // allow one reload cycle
    location.reload()
    return new Promise<T>(() => {}) // halt callers while reloading
  }
  throw err
}

Prevention

When it happens

Trigger: 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().

Common situations: 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).

Related errors


AI-assisted analysis of NousResearch/hermes-agent@c896c09c42 (2026-08-14). Data as JSON: /api/errors/69c08b7c0626fab5. Report an issue: GitHub.