odysseus-dev/odysseus · error · Error

await res.text()

Error message

await res.text()

What it means

Thrown when DELETE /api/research/{item.id} returns a non-2xx status; the entire raw response body is used as the Error message. If the server answers with an HTML error page (common for proxy 502/504 or unhandled exceptions), the user-visible toast 'Failed to delete: ...' contains a wall of markup.

Source

Thrown at static/js/documentLibrary.js:2820

        e.stopPropagation();
        const a = document.createElement('a');
        a.href = '/api/research/report/' + item.id;
        a.target = '_blank';
        a.rel = 'noopener';
        document.body.appendChild(a);
        a.click();
        a.remove();
      });
      const delBtn = preview.querySelector('.doclib-chat-delete-btn');
      if (delBtn) delBtn.addEventListener('click', async (e) => {
        e.stopPropagation();
        const ok = uiModule && uiModule.styledConfirm
          ? await uiModule.styledConfirm('Delete this research report?', { confirmText: 'Delete', danger: true })
          : window.confirm('Delete this research report?');
        if (!ok) return;
        try {
          const res = await fetch(`${API_BASE}/api/research/${item.id}`, { method: 'DELETE', credentials: 'same-origin' });
          if (!res.ok) throw new Error(await res.text());
          if (item.archived) {
            _renderLibArchive();
          } else {
            _researchItems = _researchItems.filter(r => r.id !== item.id);
            _renderResearchGrid();
          }
        } catch (err) {
          if (uiModule && uiModule.showError) uiModule.showError('Failed to delete: ' + err.message);
        }
      });
      const arcBtn = preview.querySelector('.doclib-chat-archive-btn');
      if (arcBtn) arcBtn.addEventListener('click', async (e) => {
        e.stopPropagation();
        // From the main Archive tab the item is already archived → Restore and
        // refresh the archive. From the Research tab, toggle as before.
        const fromArchiveTab = !!item.archived;
        const toArchived = fromArchiveTab ? false : !_researchArchivedView;
        try {

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Look at the DELETE /api/research/{id} response in DevTools to get the real status and reason
  2. If 404, the list is stale — reload the research grid and the item will be gone
  3. Prefer a parsed, length-limited message over raw body text: throw new Error(`HTTP ${res.status}`) with optional JSON detail
  4. On success also handle the archived branch correctly (already present) so the grid re-renders

Example fix

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

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

Strategy: try-catch

Try / catch

try { const res = await fetch(url, {method:'DELETE', credentials:'same-origin'}); if (res.status === 404) { /* already gone — treat as success */ } else if (!res.ok) { let m = `HTTP ${res.status}`; try { m=(await res.json()).detail||m; } catch {} throw new Error(m); } ... } catch (err) { uiModule?.showError('Failed to delete: ' + err.message); }

Prevention

When it happens

Trigger: Clicking Delete in a research report preview (after confirming the styled dialog) and the DELETE failing: 404 when item.id is stale, 401 after session expiry, 409/500 when the storage layer cannot remove the report.

Common situations: Item already deleted in another tab; backend restarted with an in-memory store so every id 404s; proxy-level error pages producing noisy toasts because res.text() is thrown verbatim.

Related errors


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