{"record":{"id":"54a3e123d3230112","repo":"odysseus-dev/odysseus","slug":"data-error-54a3e1","errorCode":null,"errorMessage":"data.error","messagePattern":"data\\.error","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"static/js/editor/ai-tool-runner.js","lineNumber":92,"sourceCode":"      if (sel.endpoint) extraPayload._endpoint = sel.endpoint;\n      if (sel.model && !extraPayload._model) extraPayload._model = sel.model;\n    }\n    try {\n      const flatCanvas = flatten();\n      const imageB64 = flatCanvas.toDataURL('image/png').split(',')[1];\n      const body = { image: imageB64, ...extraPayload };\n      const res = await fetch(endpoint, {\n        method: 'POST', credentials: 'same-origin',\n        headers: { 'Content-Type': 'application/json' },\n        body: JSON.stringify(body),\n      });\n      if (!res.ok) {\n        let err = res.statusText;\n        try { const e = await res.json(); err = e.detail || e.error || err; } catch {}\n        throw new Error(err);\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');\n      const img = new Image();\n      img.onload = () => {\n        if (!state.editorOpen) return; // user closed mid-decode (v2 review HIGH-4)\n        saveState();\n        const layer = createLayer(layerName, state.imgWidth, state.imgHeight);\n        layer.ctx.drawImage(img, 0, 0);\n        state.layers.push(layer);\n        state.activeLayerId = layer.id;\n        composite();\n        renderLayerPanel();\n        if (uiModule) uiModule.showToast(layerName + ' complete', 4500);\n      };\n      img.onerror = () => { if (uiModule) uiModule.showToast('Failed to load result', 6000); };\n      img.src = 'data:image/png;base64,' + data.image;\n    } catch (e) {\n      // Detect known failure modes and surface an action-toast.\n      const msg = (e?.message || '').toLowerCase();","sourceCodeStart":74,"sourceCodeEnd":110,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/static/js/editor/ai-tool-runner.js#L74-L110","documentation":"Thrown by the generic AI tool runner when the endpoint responds 200 but the JSON contains an error field — the backend swallowed an upstream/provider failure into a successful response. The message shown to the user is exactly that embedded error string.","triggerScenarios":"Provider rejects the request after the server accepted it: bad API key for the chosen model, quota/rate limit, safety filter, or a malformed provider response the server converted into {error: ...}.","commonSituations":"Rotated provider key not yet updated server-side; user picks a model the plan does not include; provider throttling during heavy editing sessions; server catch-all handlers returning 200 with error payloads.","solutions":["Use the literal data.error text to identify the provider-side cause","Correct the model/endpoint selection or provider credentials it complains about","Wait out rate limits and retry the tool once","On the backend, map provider failures to real HTTP error statuses for observability"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const data = await res.json();\nif (data.error) throw new Error(data.error);","typeGuard":"function hasImageData(d) { return 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 (!hasImageData(data)) throw new Error('No image returned'); ... } catch (e) { uiModule.showToast(`${layerName} failed: ` + e.message); }","preventionTips":["Check data.error on every 200 response from AI endpoints — this backend embeds failures in success bodies","Guard with hasImageData() before creating layers","Keep provider key/model config validated at tool-open time, not just at submit"],"tags":["ai","editor","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"}