{"record":{"id":"c7bd38ea9369acec","repo":"odysseus-dev/odysseus","slug":"data-error-data-detail-http-res-status","errorCode":null,"errorMessage":"data?.error || data?.detail || `HTTP ${res.status}`","messagePattern":"data\\?\\.error \\|\\| data\\?\\.detail \\|\\| `HTTP (.+?)`","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"static/js/document.js","lineNumber":3256,"sourceCode":"    if (kind === 'gallery') {\n      return item.caption || item.prompt || item.filename || 'Gallery image';\n    }\n    return item.title || 'Untitled document';\n  }\n\n  async function _stageOdysseusAttachment(kind, id) {\n    const doc = docs.get(activeDocId);\n    if (!doc || doc.language !== 'email') return null;\n    if (!doc._composeAtts) doc._composeAtts = [];\n    const res = await fetch(`${API_BASE}/api/email/compose-from-odysseus`, {\n      method: 'POST',\n      credentials: 'same-origin',\n      headers: { 'Content-Type': 'application/json' },\n      body: JSON.stringify({ kind, id }),\n    });\n    let data = null;\n    try { data = await res.json(); } catch (_) {}\n    if (!res.ok || !data?.success) throw new Error(data?.error || data?.detail || `HTTP ${res.status}`);\n    doc._composeAtts.push({\n      token: data.token,\n      filename: data.filename,\n      size: data.size || 0,\n    });\n    return data;\n  }\n\n  async function _stageOdysseusZip(items) {\n    const doc = docs.get(activeDocId);\n    if (!doc || doc.language !== 'email') return null;\n    if (!doc._composeAtts) doc._composeAtts = [];\n    const res = await fetch(`${API_BASE}/api/email/compose-from-odysseus-zip`, {\n      method: 'POST',\n      credentials: 'same-origin',\n      headers: { 'Content-Type': 'application/json' },\n      body: JSON.stringify({ items }),\n    });","sourceCodeStart":3238,"sourceCodeEnd":3274,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/static/js/document.js#L3238-L3274","documentation":"Thrown by _stageOdysseusAttachment in static/js/document.js when POST /api/email/compose-from-odysseus returns a non-2xx status or a JSON body without success=true. The message prefers the backend's error/detail fields and falls back to the bare HTTP status code. It means the backend refused or failed to stage an Odysseus item as an email-compose attachment token.","triggerScenarios":"POST /api/email/compose-from-odysseus with {kind, id} where the referenced Odysseus document/gallery item does not exist, the attachment file is unreadable server-side, the session cookie is missing (credentials: 'same-origin'), or a proxy returns 502/503 with a non-JSON body so data stays null and only `HTTP 502` surfaces.","commonSituations":"Clicking 'attach from Odysseus' in the email composer after the source document was deleted on the server; expired login session; reverse proxy (nginx) timing out on a large attachment; backend version that predates the endpoint returning 404.","solutions":["Check the backend response body (Network tab) — data.error/data.detail usually names the real cause (e.g. 'document not found').","Verify the kind/id being sent actually exists by listing /api/documents/library or /api/gallery/library first.","Confirm the session is authenticated (cookie present) since the request uses credentials: 'same-origin'.","If behind a proxy, raise the timeout for this endpoint because zipping/staging large attachments can exceed defaults."],"exampleFix":"// before\nif (!res.ok || !data?.success) throw new Error(data?.error || data?.detail || `HTTP ${res.status}`);\n\n// after — include which item failed so the toast is actionable\nif (!res.ok || !data?.success) {\n  const reason = data?.error || data?.detail || `HTTP ${res.status}`;\n  throw new Error(`Failed to attach ${kind}/${id}: ${reason}`);\n}","handlingStrategy":"try-catch","validationCode":"if (!kind || !id) return; // nothing to stage\nconst doc = docs.get(activeDocId);\nif (!doc || doc.language !== 'email') return;","typeGuard":"function isStagedAttachment(data) {\n  return data != null && typeof data === 'object'\n    && data.success === true\n    && typeof data.token === 'string' && data.token.length > 0\n    && typeof data.filename === 'string';\n}","tryCatchPattern":"try {\n  const data = await _stageOdysseusAttachment(kind, id);\n  if (!data) return;\n} catch (err) {\n  console.error('Odysseus attach failed:', err);\n  if (uiModule) uiModule.showError(`Attachment failed: ${err.message}`);\n}","preventionTips":["Verify the item still exists via the library listing before offering it in the attach menu.","Keep frontend and backend deployed together so the compose-from-odysseus contract (token/filename/size) stays in sync.","Handle auth expiry globally (401 interceptor / re-login prompt) instead of per call site."],"tags":["http","fetch","email-compose","attachments","api-error"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}