odysseus-dev/odysseus · warning · Error

HTTP ${res.status}

Error message

HTTP ${res.status}

What it means

Error thrown when downloading an email attachment from a rendered email document: GET /api/email/attachment/{uid}/{index}?folder=... answered non-OK. Only 'HTTP <status>' is reported to the user via showError('Download failed: ...').

Source

Thrown at static/js/document.js:3017

                if (uiModule) uiModule.showError('Failed to open PDF');
              }
            }));
            attDiv.appendChild(chip);
          } else {
            // Non-PDF: download via fetch+blob+anchor — browser-native download
            // with target=_blank was unreliable in some browsers (the click did
            // nothing). The blob path forces a real Save dialog every time.
            const chip = document.createElement('button');
            chip.type = 'button';
            chip.className = 'email-attachment-chip';
            // Full filename on hover for the chip ellipsis-truncated label.
            chip.title = `Download ${att.filename}`;
            chip.innerHTML = chipHtml;
            chip.addEventListener('click', () => _withSpinner(chip, async () => {
              try {
                const folderQs = encodeURIComponent(fields.sourceFolder || 'INBOX');
                const res = await fetch(`${API_BASE}/api/email/attachment/${encodeURIComponent(fields.sourceUid)}/${att.index}?folder=${folderQs}`);
                if (!res.ok) throw new Error(`HTTP ${res.status}`);
                const blob = await res.blob();
                const url = URL.createObjectURL(blob);
                const a = document.createElement('a');
                a.href = url; a.download = att.filename;
                document.body.appendChild(a);
                a.click();
                a.remove();
                setTimeout(() => URL.revokeObjectURL(url), 1000);
              } catch (e) {
                console.error('Download attachment failed:', e);
                if (uiModule) uiModule.showError('Download failed: ' + e.message);
              }
            }));
            attDiv.appendChild(chip);
          }
        }
      } else {
        attDiv.style.display = 'none';

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Re-render/reload the email document so uid/folder/index are fresh, then retry
  2. Verify the message still exists in that folder via a normal mail client
  3. Check backend logs/IMAP connectivity for 500/502s
  4. If 404 persists, the attachment was expunged — it must be re-requested from the sender

Example fix

// before
if (!res.ok) throw new Error(`HTTP ${res.status}`);

// after
if (!res.ok) {
  const t = await res.text().catch(() => '');
  let msg = '';
  try { msg = JSON.parse(t).detail || ''; } catch { msg = t.slice(0, 160); }
  throw new Error(`HTTP ${res.status}${msg ? ': ' + msg : ''}`);
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (!fields.sourceUid || !att || typeof att.index !== 'number') { uiModule?.showError?.('Attachment link is stale — reload the email'); return; }

Try / catch

try {
  const res = await fetch(`${API_BASE}/api/email/attachment/${encodeURIComponent(fields.sourceUid)}/${att.index}?folder=${encodeURIComponent(fields.sourceFolder || 'INBOX')}`);
  if (!res.ok) {
    const t = await res.text().catch(() => '');
    let msg = '';
    try { msg = JSON.parse(t).detail || ''; } catch { msg = t.slice(0, 160); }
    throw new Error(`HTTP ${res.status}${msg ? ': ' + msg : ''}`);
  }
  const blob = await res.blob();
  // ... download
} catch (e) {
  if (uiModule) uiModule.showError('Download failed: ' + (e.message || e));
}

Prevention

When it happens

Trigger: GET attachment returns 404 (uid no longer in the folder — mail moved/expunged, or wrong folder name), 410 (attachment expired from cache), 500 (IMAP fetch crashed), 502 (mail server unreachable from backend).

Common situations: Mail client (phone/other) moved or deleted the message between render and click; folder renamed on the server; IMAP connection pool broken after backend idle; index beyond the message's attachment count after a re-render with stale data.

Related errors


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