{"record":{"id":"a875e9ecaa085253","repo":"odysseus-dev/odysseus","slug":"http-res-status-a875e9","errorCode":null,"errorMessage":"HTTP ${res.status}","messagePattern":"HTTP \\$\\{res\\.status\\}","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"static/js/document.js","lineNumber":3017,"sourceCode":"                if (uiModule) uiModule.showError('Failed to open PDF');\n              }\n            }));\n            attDiv.appendChild(chip);\n          } else {\n            // Non-PDF: download via fetch+blob+anchor — browser-native download\n            // with target=_blank was unreliable in some browsers (the click did\n            // nothing). The blob path forces a real Save dialog every time.\n            const chip = document.createElement('button');\n            chip.type = 'button';\n            chip.className = 'email-attachment-chip';\n            // Full filename on hover for the chip ellipsis-truncated label.\n            chip.title = `Download ${att.filename}`;\n            chip.innerHTML = chipHtml;\n            chip.addEventListener('click', () => _withSpinner(chip, async () => {\n              try {\n                const folderQs = encodeURIComponent(fields.sourceFolder || 'INBOX');\n                const res = await fetch(`${API_BASE}/api/email/attachment/${encodeURIComponent(fields.sourceUid)}/${att.index}?folder=${folderQs}`);\n                if (!res.ok) throw new Error(`HTTP ${res.status}`);\n                const blob = await res.blob();\n                const url = URL.createObjectURL(blob);\n                const a = document.createElement('a');\n                a.href = url; a.download = att.filename;\n                document.body.appendChild(a);\n                a.click();\n                a.remove();\n                setTimeout(() => URL.revokeObjectURL(url), 1000);\n              } catch (e) {\n                console.error('Download attachment failed:', e);\n                if (uiModule) uiModule.showError('Download failed: ' + e.message);\n              }\n            }));\n            attDiv.appendChild(chip);\n          }\n        }\n      } else {\n        attDiv.style.display = 'none';","sourceCodeStart":2999,"sourceCodeEnd":3035,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/static/js/document.js#L2999-L3035","documentation":"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: ...').","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":"// before\nif (!res.ok) throw new Error(`HTTP ${res.status}`);\n\n// after\nif (!res.ok) {\n  const t = await res.text().catch(() => '');\n  let msg = '';\n  try { msg = JSON.parse(t).detail || ''; } catch { msg = t.slice(0, 160); }\n  throw new Error(`HTTP ${res.status}${msg ? ': ' + msg : ''}`);\n}","handlingStrategy":"try-catch","validationCode":"if (!fields.sourceUid || !att || typeof att.index !== 'number') { uiModule?.showError?.('Attachment link is stale — reload the email'); return; }","typeGuard":null,"tryCatchPattern":"try {\n  const res = await fetch(`${API_BASE}/api/email/attachment/${encodeURIComponent(fields.sourceUid)}/${att.index}?folder=${encodeURIComponent(fields.sourceFolder || 'INBOX')}`);\n  if (!res.ok) {\n    const t = await res.text().catch(() => '');\n    let msg = '';\n    try { msg = JSON.parse(t).detail || ''; } catch { msg = t.slice(0, 160); }\n    throw new Error(`HTTP ${res.status}${msg ? ': ' + msg : ''}`);\n  }\n  const blob = await res.blob();\n  // ... download\n} catch (e) {\n  if (uiModule) uiModule.showError('Download failed: ' + (e.message || e));\n}","preventionTips":["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)"],"tags":["fetch","http-error","email","attachment","imap"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}