odysseus-dev/odysseus · error · Error
data.error
Error message
data.error
What it means
Thrown in the email library's paginated list loader when GET /api/email/list returns a body with an error field. Distinctive context: a sequence guard (seq !== _libLoadSeq) already cancels stale responses, so seeing this means the request is current and the server genuinely reported a mailbox failure.
Source
Thrown at static/js/emailLibrary.js:4803
grid.classList.remove('email-lib-just-opened');
paintData(fastData);
paintedExisting = true;
if (!force) return;
}
} catch (_) {
// Cold index miss/timeout: leave the spinner and continue to IMAP.
} finally {
clearTimeout(timer);
}
}
// `&_=Date.now()` bypasses the server's 8s list cache. Default
// opens omit it so rapid close/reopen returns instantly; the
// Refresh button passes `force: true` to add it back.
const buster = force ? `&_=${Date.now()}` : '';
const res = await fetch(`${API_BASE}/api/email/list?folder=${encodeURIComponent(folderAtStart)}${accountQS}&limit=${_LIB_INITIAL_PAGE_SIZE}&offset=${offsetAtStart}&filter=${filterAtStart}${attQS}${buster}`);
const data = await res.json();
if (seq !== _libLoadSeq || accountAtStart !== (state._libAccountId || '')) return;
if (data.error) throw new Error(data.error);
const sync = data.sync || {};
if (sp) sp.destroy();
paintData({ emails: data.emails || [], total: data.total || 0, sync });
if (filterAtStart === 'unread') {
_refreshUnreadBadge({ unreadCountOverride: data.total || 0 });
} else {
_refreshUnreadBadge();
_refreshAccountUnreadHighlights().catch(() => {});
}
}
} catch (e) {
if (seq !== _libLoadSeq || accountAtStart !== (state._libAccountId || '')) return;
if (sp) sp.destroy();
// If we already painted the cached list, leave it on screen — beats
// wiping it for "Failed to load" when there's still readable content.
if (!paintedExisting) {
const msg = e && e.message ? `Failed to load: ${e.message}` : 'Failed to load';
grid.innerHTML = `<div class="email-loading">${_esc(msg)}${_emailSetupHintHtml()}</div>`;View on GitHub (pinned to f9235ebbf1)
Solutions
- Read data.error for the server's reason (usually account/folder + IMAP detail)
- Re-authenticate or re-add the email account in settings
- Retry with the Refresh button (cache-busting _) once the provider is healthy
- If a specific folder always fails, open it less or fix its server-side select error
Defensive patterns
Strategy: try-catch
Type guard
function isLibListOk(d) { return d != null && !d.error && Array.isArray(d.emails); } Try / catch
try { const data = await res.json(); if (seq !== _libLoadSeq || accountAtStart !== (state._libAccountId || '')) return; if (data.error) throw new Error(data.error); ... } catch (e) { if (seq !== _libLoadSeq) return; /* render error, keep spinner handling */ } Prevention
- Keep the sequence guard on both success and error paths so stale errors never paint
- Retry with the cache-busting &_= param after provider outages
- Re-auth accounts proactively when error strings mention credentials
- Let the cold-index spinner fall through to IMAP as coded — don't error on cache misses
When it happens
Trigger: Opening the library, paging, filtering, or pressing Refresh (which adds &_=Date.now() to bust the 8s server cache) when the IMAP fetch fails: expired credentials, provider outage, folder select error, or an account_id that no longer exists. Cold-index spinner/timeout path deliberately falls through to IMAP, so this is the authoritative call failing.
Common situations: OAuth token expiry mid-session; huge folder timing out server-side; account deleted from settings while the library was open; server's 8s cache serving an error payload from a prior failed sync.
Related errors
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/8b52767163351d8a.
Report an issue: GitHub.