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
- Inspect the actual status code (res.status) and response body in DevTools Network tab to identify why /api/research/library failed
- Include res.status in the thrown message so it is never blank: throw new Error(`HTTP ${res.status}`)
- If the server returns a JSON error body, parse it and prefer its detail/error field over statusText
- 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
- Never use bare res.statusText as the only message — it is empty over HTTP/2
- Always include res.status in fallback error messages
- Parse the JSON error body (detail/error) before falling back
- Handle 401 with a session-refresh path instead of showing a raw failure
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.