{"record":{"id":"d0fa22f20587d103","repo":"odysseus-dev/odysseus","slug":"data-detail-data-error-mask-failed-res-s","errorCode":null,"errorMessage":"data.detail || data.error || `Mask failed (${res.status})`","messagePattern":"data\\.detail \\|\\| data\\.error \\|\\| `Mask failed \\((.+?)\\)`","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"static/js/galleryEditor.js","lineNumber":2008,"sourceCode":"      btn.disabled = false;\n      btn.innerHTML = old || '<span class=\"ge-btn-ai-mark\" aria-hidden=\"true\">✦</span>Find';\n    }\n  }\n}\n\nasync function _requestAndApplySamMask(layer, payload, mode, seedPoint, opts = {}) {\n  const res = await fetch('/api/image/mask', {\n      method: 'POST',\n      headers: { 'Content-Type': 'application/json' },\n      signal: opts.signal,\n      body: JSON.stringify({\n        image: layer.canvas.toDataURL('image/png').split(',')[1],\n        ...payload,\n      }),\n    });\n  const data = await res.json().catch(() => ({}));\n  if (!res.ok || !data.mask) {\n    throw new Error(data.detail || data.error || `Mask failed (${res.status})`);\n  }\n  if (!data.bbox) {\n    throw new Error(data.grounding ? `Found ${data.grounding.label || 'object'}, but SAM returned an empty mask` : 'SAM returned an empty mask');\n  }\n\n  const img = new Image();\n  await new Promise((resolve, reject) => {\n    img.onload = resolve;\n    img.onerror = () => reject(new Error('Failed to decode mask'));\n    img.src = 'data:image/png;base64,' + data.mask;\n  });\n  const mask = document.createElement('canvas');\n  mask.width = layer.canvas.width;\n  mask.height = layer.canvas.height;\n  const mctx = mask.getContext('2d');\n  mctx.drawImage(img, 0, 0, mask.width, mask.height);\n  const maskData = mctx.getImageData(0, 0, mask.width, mask.height);\n  const md = maskData.data;","sourceCodeStart":1990,"sourceCodeEnd":2026,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/static/js/galleryEditor.js#L1990-L2026","documentation":"Thrown by _requestAndApplySamMask in galleryEditor.js when POST /api/image/mask either returns a non-ok status or a 2xx body without a mask field. The message prefers the server's detail (FastAPI style) or error field, falling back to 'Mask failed (status)'. A json parse failure is swallowed to {} so HTML error pages degrade to the status form.","triggerScenarios":"Clicking an AI select/mask tool (SAM) in the photo editor when the backend grounding/DINO endpoint is not configured; the SAM service is down or unlicensed; the request body's base64 image is rejected (too large, malformed); opts.signal aborted mid-request.","commonSituations":"Editor deployed without the SAM/Grounding service running; model weights missing after an update; a very high-resolution layer whose dataURL exceeds the server's body limit (413); aborted requests when the user cancels mid-inference surface as this generic error.","solutions":["Confirm the image/mask backend service is running and reachable from the app server.","Check the response detail/error text — it names the missing model or config (e.g. SAM weights not found).","If the image is very large, downscale the layer or raise the server's request body limit before retrying.","Reinstall/download the SAM model assets the endpoint reports as missing."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"if (!layer?.canvas) throw new Error('No active layer to mask');\nconst dataUrl = layer.canvas.toDataURL('image/png');\nif (dataUrl.length > 8_000_000) throw new Error('Image too large for mask request — downscale first');","typeGuard":"const isMaskResponse = (d) => d && typeof d.mask === 'string' && (Array.isArray(d.bbox) || d.bbox === undefined);","tryCatchPattern":"try { const data = await res.json().catch(() => ({})); if (!res.ok || !data.mask) throw new Error(data.detail || data.error || `Mask failed (${res.status})`); } catch (e) { if (e.name === 'AbortError') return; showError(e.message); }","preventionTips":["Downscale oversized canvases before base64-encoding the payload","Pass opts.signal and special-case AbortError so cancellations are not shown as failures","Health-check the mask service before enabling the AI select tools"],"tags":["image-processing","sam","segmentation","http","editor"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}