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

  1. Read data.error for the server's reason (usually account/folder + IMAP detail)
  2. Re-authenticate or re-add the email account in settings
  3. Retry with the Refresh button (cache-busting _) once the provider is healthy
  4. 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

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.