odysseus-dev/odysseus · error · Error

statusText

Error message

statusText

What it means

Thrown when the fetch to GET /api/research/library returns a non-2xx status. The code throws new Error(res.statusText), so the message is whatever the server's status line carries. Note that over HTTP/2 (and many proxies) statusText is an empty string, so the surfaced message can literally be blank.

Source

Thrown at static/js/documentLibrary.js:2692

    let _researchSelectMode = false;
    let _researchArchivedView = false;
    const _researchSelected = new Set();

    async function _renderLibResearch() {
      const grid = document.getElementById('doclib-research-grid');
      const stats = document.getElementById('doclib-research-stats');
      if (!grid) return;
      // Show our whirlpool spinner instead of the plain "Loading..." text.
      grid.innerHTML = '';
      try {
        const _spm = (await import('./spinner.js')).default;
        const _sp = _spm.createWhirlpool(22);
        _sp.element.style.cssText = 'margin:18px auto;display:block;';
        grid.appendChild(_sp.element);
      } catch { grid.innerHTML = '<div class="hwfit-loading">Loading…</div>'; }
      try {
        const res = await fetch('/api/research/library' + (_researchArchivedView ? '?archived=true' : ''), { credentials: 'same-origin' });
        if (!res.ok) throw new Error(res.statusText);
        const data = await res.json();
        _researchItems = data.research || data || [];
      } catch (e) {
        grid.innerHTML = `<div class="hwfit-loading">Failed to load: ${_esc(e.message)}</div>`;
        return;
      }
      _renderResearchGrid();
    }

    // Toggle inline preview for a research row. Mirrors _toggleChatPreview
     // but pulls research-specific metadata: query, sources list (truncated),
     // followed by an "Open" action that loads the full report.
    async function _toggleResearchPreview(card, item) {
      const preview = card.querySelector('.doclib-chat-preview');
      if (!preview) return;
      const isOpen = card.classList.contains('doclib-card-expanded');
      const grid = card.closest('.doclib-grid');
      if (grid) {

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Inspect the actual status code (res.status) and response body in DevTools Network tab to identify why /api/research/library failed
  2. Include res.status in the thrown message so it is never blank: throw new Error(`HTTP ${res.status}`)
  3. If the server returns a JSON error body, parse it and prefer its detail/error field over statusText
  4. If 401/403, re-authenticate or reload the session before retrying the library load

Example fix

// before
if (!res.ok) throw new Error(res.statusText);

// after
if (!res.ok) {
  let msg = `HTTP ${res.status}`;
  try { const b = await res.json(); msg = b.detail || b.error || msg; } catch {}
  throw new Error(msg);
}
Defensive patterns

Strategy: try-catch

Try / catch

try { const res = await fetch(url, {credentials:'same-origin'}); if (!res.ok) { let m = `HTTP ${res.status}`; try { m = (await res.json()).detail || m; } catch {} throw new Error(m); } ... } catch (e) { grid.innerHTML = `<div class="hwfit-loading">Failed to load: ${_esc(e.message)}</div>`; }

Prevention

When it happens

Trigger: GET /api/research/library (optionally ?archived=true) responding with 4xx/5xx — e.g. 401 after session expiry, 500 if the research store fails to load, or a dev-server 404 when the API route is not mounted.

Common situations: Expired auth cookie on a long-open tab; backend restarted and lost state; running the static frontend against a server that does not expose the research API; HTTP/2 yielding empty statusText so users see 'Failed to load: ' with no reason.

Related errors


AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14). Data as JSON: /api/errors/679c8ccdc78cf4d1. Report an issue: GitHub.