{"record":{"id":"cdcb0b7ea2f9b2f8","repo":"odysseus-dev/odysseus","slug":"data-error-cdcb0b","errorCode":null,"errorMessage":"data.error","messagePattern":"data\\.error","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"static/js/editor/ai-inpaint.js","lineNumber":157,"sourceCode":"      const baseSnap = document.createElement('canvas');\n      baseSnap.width = state.imgWidth;\n      baseSnap.height = state.imgHeight;\n      baseSnap.getContext('2d').drawImage(flatCanvas, 0, 0);\n      const res = await fetch('/api/image/inpaint', {\n        method: 'POST', credentials: 'same-origin',\n        headers: { 'Content-Type': 'application/json' },\n        body: JSON.stringify((() => {\n          const sel = getSelectedAIEndpoint('inpaint');\n          return { image: imageB64, mask: maskB64, prompt, width: state.imgWidth, height: state.imgHeight, strength, feather: 0, _endpoint: sel.endpoint, _model: sel.model };\n        })()),\n      });\n      if (!res.ok) {\n        let errDetail = res.statusText;\n        try { const errBody = await res.json(); errDetail = errBody.detail || errBody.error || errDetail; } catch {}\n        throw new Error(errDetail);\n      }\n      const data = await res.json();\n      if (data.error) throw new Error(data.error);\n      if (!data.image) throw new Error('No image returned from inpaint endpoint');\n      // Load result as a new layer and clip with the user-drawn mask\n      // so only the inpainted region is visible. Cache the\n      // unfeathered (AI image + hard mask) on the layer so the live\n      // Feather slider can re-derive the alpha on each input event\n      // without re-running the model.\n      const resultImg = new Image();\n      resultImg.onload = () => {\n        if (!state.editorOpen) return; // user closed mid-decode\n        try {\n          saveState('Inpaint result');\n          // OpenAI returns at one of its allowed sizes (1024²,\n          // 1024×1536, 1536×1024) which often differs from our\n          // canvas. Scale to canvas size with smoothing so the\n          // inpaint blends in regardless of source dims.\n          const shortPrompt = (prompt || '').trim().replace(/\\s+/g, ' ').slice(0, 40);\n          const layerName = shortPrompt ? `Inpaint: ${shortPrompt}` : 'Inpaint Result';\n          const resultLayer = createLayer(layerName, state.imgWidth, state.imgHeight);","sourceCodeStart":139,"sourceCodeEnd":175,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/static/js/editor/ai-inpaint.js#L139-L175","documentation":"Thrown when POST /api/image/inpaint returns HTTP 200 but the JSON body carries an error field. This pattern means the backend chose to report a failure (typically an upstream AI provider error) inside a successful response instead of a non-2xx status.","triggerScenarios":"The provider call inside the server fails after the request was accepted: invalid model name for the selected endpoint, provider quota exhausted, content policy rejection, or a provider timeout — the server wraps that as {error: '...'} with 200.","commonSituations":"Switching _model to one the key does not have access to; provider rate limits hit mid-session; NSFW filter triggering on the prompt/mask region; server code that catches upstream exceptions and returns data.error rather than raising HTTPException.","solutions":["Read the data.error string — it is the upstream provider message and names the real cause","Fix the configuration it points at (valid model name for the endpoint, quota, prompt wording)","Retry after clearing the rate-limit window if the error mentions 429/quota","If you own the backend, return proper 4xx/5xx statuses instead of 200-with-error so monitoring counts these"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const data = await res.json();\nif (data.error) throw new Error(data.error);","typeGuard":"function isInpaintSuccess(d) { return typeof d === 'object' && d !== null && typeof d.image === 'string' && d.image.length > 0; }","tryCatchPattern":"try { const data = await res.json(); if (data.error) throw new Error(data.error); if (!isInpaintSuccess(data)) throw new Error('No image returned from inpaint endpoint'); ... } catch (e) { uiModule.showToast('Inpaint failed: ' + e.message); }","preventionTips":["Always check both transport (!res.ok) and application (data.error) failure modes","Validate provider config (key, model) before long-running inpaint calls","Prefer backends that return real 4xx/5xx for provider failures so monitoring sees them","Type-guard the success shape before touching data.image"],"tags":["ai","inpaint","error-in-body","upstream-provider","http-200-error"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}