odysseus-dev/odysseus · error · Error

detail || `HTTP

Error message

detail || `HTTP ${res.status}`

What it means

Thrown by _chatAboutResearch in research/panel.js when POST /api/research/spinoff/{researchId} fails. The client attempts to extract a FastAPI-style detail from the JSON body; if absent or unparseable it falls back to the bare HTTP status. The catch re-enables the button and alerts, so UI state is restored.

Solutions

  1. Close and reopen the research panel to refresh ids, then retry.
  2. Read the detail in the alert for the server's reason; check server logs for 5xx.
  3. Re-authenticate if the status is 401.
  4. Verify the research entry still exists in the panel list.
Defensive patterns

Strategy: try-catch

Validate before calling

if (!researchId) { btn.disabled = false; return; }

Type guard

const isSpinoffPayload = (p) => !!p && !!p.session_id;

Try / catch

try { ... } catch (e) { btn.disabled = false; btn.innerHTML = origLabel; alert('Could not start follow-up chat: ' + e.message); }

Prevention

When it happens

Trigger: Creating a follow-up chat from a research report whose id no longer exists (404); research data deleted between panel open and click; server cannot create the spinoff session (db error → 500 with detail); auth expired (401 with empty body → 'HTTP 401').

Common situations: Stale research panel left open while the server restarted or the research store was cleared; long-lived tabs with expired sessions.

Related errors


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

Appendix: source

Thrown at static/js/research/panel.js:1226

      setTimeout(() => { btn.innerHTML = orig; }, 2000);
    }
  }
}

// ── Chat about this research (server-side spinoff) ──

async function _chatAboutResearch(researchId, btn) {
  if (!researchId) return;
  const origLabel = btn ? btn.innerHTML : '';
  if (btn) { btn.disabled = true; btn.innerHTML = `${_chatIcon} Creating…`; }
  try {
    const res = await fetch(`${_apiBase}/api/research/spinoff/${researchId}`, {
      method: 'POST', credentials: 'same-origin',
    });
    if (!res.ok) {
      let detail = '';
      try { detail = (await res.json()).detail || ''; } catch {}
      throw new Error(detail || `HTTP ${res.status}`);
    }
    const payload = await res.json();
    if (_sessionModule && _sessionModule.selectSession && payload.session_id) {
      if (_sessionModule.loadSessions) await _sessionModule.loadSessions().catch(() => {});
      await _sessionModule.selectSession(payload.session_id);
      closePanel();
    } else if (payload.session_id) {
      window.location.hash = '#' + payload.session_id;
      window.location.reload();
    } else {
      // 200 OK but no session_id — server contract violation. Don't leave
      // the button stuck on 'Creating…'; surface the failure instead.
      throw new Error('Server returned no session id');
    }
  } catch (e) {
    if (btn) { btn.disabled = false; btn.innerHTML = origLabel; }
    alert('Could not start follow-up chat: ' + e.message);
  }

View on GitHub (pinned to f9235ebbf1)