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
- Re-render/reload the email document so uid/folder/index are fresh, then retry
- Verify the message still exists in that folder via a normal mail client
- Check backend logs/IMAP connectivity for 500/502s
- 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
- Read and surface the error body — bare 'HTTP 404' hides the expunged-message cause
- Treat 404 as 'message moved or deleted' and suggest re-rendering the email
- Always encodeURIComponent uid and folder (uid already is; folder uses encodeURIComponent — keep both)
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
- data.error || 'failed'
- data.error
- data.error
- detail || ('HTTP ' + res.status)
- errData.detail || 'Failed to create session'
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/a875e9ecaa085253.
Report an issue: GitHub.