{"record":{"id":"15334cc88faa92b9","repo":"odysseus-dev/odysseus","slug":"data-data-error-data-detail-http-r","errorCode":null,"errorMessage":"(data && (data.error || data.detail)) || `HTTP ${res.status}`","messagePattern":"\\(data && \\(data\\.error \\|\\| data\\.detail\\)\\) \\|\\| `HTTP (.+?)`","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"static/js/document.js","lineNumber":3571,"sourceCode":"      if (uiModule) uiModule.showError('Choose an image file');\n      return;\n    }\n    if (_activeDocLanguage() !== 'markdown') {\n      if (uiModule) uiModule.showError('Switch the document to markdown before inserting images');\n      return;\n    }\n\n    const fd = new FormData();\n    images.forEach(file => fd.append('files', file));\n    try {\n      const res = await fetch(`${API_BASE}/api/upload`, {\n        method: 'POST',\n        credentials: 'same-origin',\n        body: fd,\n      });\n      let data = null;\n      try { data = await res.json(); } catch (_) {}\n      if (!res.ok) throw new Error((data && (data.error || data.detail)) || `HTTP ${res.status}`);\n      const uploaded = Array.isArray(data?.files) ? data.files : [];\n      if (!uploaded.length) throw new Error('No uploaded files returned');\n      _insertMarkdownImages(uploaded);\n      if (uiModule) uiModule.showToast(images.length === 1 ? 'Image inserted' : 'Images inserted');\n    } catch (err) {\n      console.error('Failed to insert markdown image:', err);\n      if (uiModule) uiModule.showError('Failed to insert image');\n    }\n  }\n\n  async function _handleMarkdownImageUpload(e) {\n    const files = e.target.files;\n    e.target.value = '';\n    await _uploadMarkdownImages(files);\n  }\n\n  function _renderComposeAttachments() {\n    const container = document.getElementById('doc-email-compose-atts');","sourceCodeStart":3553,"sourceCodeEnd":3589,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/static/js/document.js#L3553-L3589","documentation":"Thrown by the markdown image upload path in static/js/document.js when POST /api/upload (multipart FormData of one or more 'files') returns non-2xx. The message prefers data.error/data.detail and falls back to `HTTP <status>`; because res.json() is already wrapped in try/catch, a non-JSON body yields the plain status string.","triggerScenarios":"Pasting/dragging images into a markdown document when the upload exceeds the server's max body size (413), an unsupported file type is rejected (400), the uploads directory is unwritable (500), or auth fails (401).","commonSituations":"Screenshot pastes that are several MB hitting a 1 MB default proxy limit (client_max_body_size); nginx 502 during upload; disk full on the server.","solutions":["Check the exact status: 413 means raise the upload size limit (e.g. nginx client_max_body_size or the backend's multipart cap).","Verify the field name is 'files' and Content-Type is unset (browser sets multipart boundary) — do not set the JSON header for FormData.","Confirm the server's upload directory exists and is writable.","Compress images client-side before upload if large pastes are common."],"exampleFix":"// before\nconst res = await fetch(`${API_BASE}/api/upload`, { method: 'POST', credentials: 'same-origin', body: fd });\n\n// after — surface the server's reason in the toast\nconst res = await fetch(`${API_BASE}/api/upload`, { method: 'POST', credentials: 'same-origin', body: fd });\nif (!res.ok) {\n  let reason = `HTTP ${res.status}`;\n  try { const j = await res.json(); reason = j?.error || j?.detail || reason; } catch (_) {}\n  throw new Error(`Upload failed: ${reason}`);\n}","handlingStrategy":"try-catch","validationCode":"const okTypes = ['image/png', 'image/jpeg', 'image/gif', 'image/webp', 'image/svg+xml'];\nconst images = Array.from(files || []).filter(f => okTypes.includes(f.type) && f.size > 0 && f.size <= MAX_BYTES);","typeGuard":"function isUploadResult(data) {\n  return data != null && typeof data === 'object' && Array.isArray(data.files) && data.files.length > 0;\n}","tryCatchPattern":"try {\n  const res = await fetch(`${API_BASE}/api/upload`, { method: 'POST', credentials: 'same-origin', body: fd });\n  let data = null;\n  try { data = await res.json(); } catch (_) {}\n  if (!res.ok) throw new Error((data && (data.error || data.detail)) || `HTTP ${res.status}`);\n  if (!isUploadResult(data)) throw new Error('No uploaded files returned');\n  _insertMarkdownImages(data.files);\n} catch (err) {\n  if (uiModule) uiModule.showError(`Failed to insert image: ${err.message}`);\n}","preventionTips":["Never set Content-Type manually on FormData requests; let the browser set the multipart boundary.","Enforce a client-side size cap matching the server's limit before uploading.","Compress/downscale pasted screenshots before upload."],"tags":["http","file-upload","multipart","markdown","api-error"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}