odysseus-dev/odysseus · error · Error

Session request returned an invalid response

Error message

Session request returned an invalid response

What it means

Thrown by loadSessions in sessions.js when the sessions request succeeded (or was cached) but res.json() did not yield an array. It is distinct from the HTTP-status error just above it ('Session request failed (HTTP n)'), which carries .status — this one means the payload shape is wrong despite a 2xx.

Source

Thrown at static/js/sessions.js:1699

      let url = `${API_BASE}/api/sessions`;
      if (currentSessionId && _isIncognitoSession(currentSessionId)) {
        url += `?active_incognito_id=${encodeURIComponent(currentSessionId)}`;
      }
      const res = await fetch(url);
      if (!res.ok) {
        let detail = '';
        try {
          const payload = await res.json();
          detail = payload?.detail || payload?.error || '';
        } catch (_) {}
        const error = new Error(detail || `Session request failed (HTTP ${res.status})`);
        error.status = res.status;
        throw error;
      }
      fetched = await res.json();
    }
    if (!Array.isArray(fetched)) {
      throw new Error('Session request returned an invalid response');
    }
    sessions = _normalizeSessionsList(fetched);
    renderSessionList();

    const sessionsSection = uiModule.el('sessions-section');
    if (sessions.length === 0) {
      sessionsSection.classList.add('hidden');
    } else {
      sessionsSection.classList.remove('hidden');
    }

    const activeSessions = sessions.filter(s => !s.archived);
    // "Transient" sessions = the singleton Assistant chat + any task-output
    // session. Treat them as not-restorable so coming back to the app lands
    // on the user's last actual conversation, not whichever check-in task
    // most recently appended a message.
    const _isTransient = (s) => !!s && (s.folder === 'Assistant' || s.folder === 'Tasks');
    const _realSessions = activeSessions.filter(s => !_isTransient(s));

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Inspect the network response for /api/session* — compare its top-level shape with Array.isArray expectation.
  2. If wrapped, unwrap in the client (see exampleFix) or revert the server to a bare array.
  3. Fix proxy/SPA fallback config so API routes are not served index.html.
  4. Ensure auth redirects return JSON errors, not HTML.

Example fix

// before
fetched = await res.json();
if (!Array.isArray(fetched)) { throw new Error('Session request returned an invalid response'); }

// after (accept wrapped envelope)
fetched = await res.json();
const list = Array.isArray(fetched) ? fetched : fetched?.sessions ?? fetched?.items;
if (!Array.isArray(list)) { throw new Error('Session request returned an invalid response'); }
sessions = _normalizeSessionsList(list);
Defensive patterns

Strategy: type-guard

Type guard

const isSessionList = (v) => Array.isArray(v) || Array.isArray(v?.sessions) || Array.isArray(v?.items);

Try / catch

try { fetched = await res.json(); const list = Array.isArray(fetched) ? fetched : fetched?.sessions ?? fetched?.items; if (!Array.isArray(list)) throw new Error('Session request returned an invalid response'); } catch (e) { showError(e.message); renderSessionList(); /* render cached/empty rather than blank */ }

Prevention

When it happens

Trigger: The sessions endpoint returning a wrapped object like {sessions:[...]} instead of a bare array; a 200 HTML page from a misrouted proxy or the SPA index fallback; a server version change to a paginated envelope {items, total}; JSON parsing of an empty body producing undefined.

Common situations: Frontend/backend version skew after an API envelope refactor; catch-all SPA routing serving index.html for /api/session on misconfigured hosts; login pages returned in place of JSON when auth middleware redirects.

Related errors


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