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
- Look at the DELETE /api/research/{id} response in DevTools to get the real status and reason
- If 404, the list is stale — reload the research grid and the item will be gone
- Prefer a parsed, length-limited message over raw body text: throw new Error(`HTTP ${res.status}`) with optional JSON detail
- 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
- Never throw raw res.text() — HTML error pages leak markup into toasts
- Treat 404 on DELETE as idempotent success when the list can be refreshed
- Re-render the grid from server state after deletions instead of local filtering only
- Confirm destructive actions (already done here) so accidental double-deletes surface as 404s not errors
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
- statusText
- statusText
- statusText
- Server returned ${res.status}
- data?.error || data?.detail || `HTTP ${res.status}`
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/f1beaf15b1327742.
Report an issue: GitHub.