odysseus-dev/odysseus · error · Error

data.error

Error message

data.error

What it means

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.

Source

Thrown at static/js/editor/ai-tool-runner.js:92

      if (sel.endpoint) extraPayload._endpoint = sel.endpoint;
      if (sel.model && !extraPayload._model) extraPayload._model = sel.model;
    }
    try {
      const flatCanvas = flatten();
      const imageB64 = flatCanvas.toDataURL('image/png').split(',')[1];
      const body = { image: imageB64, ...extraPayload };
      const res = await fetch(endpoint, {
        method: 'POST', credentials: 'same-origin',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(body),
      });
      if (!res.ok) {
        let err = res.statusText;
        try { const e = await res.json(); err = e.detail || e.error || err; } catch {}
        throw new Error(err);
      }
      const data = await res.json();
      if (data.error) throw new Error(data.error);
      if (!data.image) throw new Error('No image returned');
      const img = new Image();
      img.onload = () => {
        if (!state.editorOpen) return; // user closed mid-decode (v2 review HIGH-4)
        saveState();
        const layer = createLayer(layerName, state.imgWidth, state.imgHeight);
        layer.ctx.drawImage(img, 0, 0);
        state.layers.push(layer);
        state.activeLayerId = layer.id;
        composite();
        renderLayerPanel();
        if (uiModule) uiModule.showToast(layerName + ' complete', 4500);
      };
      img.onerror = () => { if (uiModule) uiModule.showToast('Failed to load result', 6000); };
      img.src = 'data:image/png;base64,' + data.image;
    } catch (e) {
      // Detect known failure modes and surface an action-toast.
      const msg = (e?.message || '').toLowerCase();

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Use the literal data.error text to identify the provider-side cause
  2. Correct the model/endpoint selection or provider credentials it complains about
  3. Wait out rate limits and retry the tool once
  4. On the backend, map provider failures to real HTTP error statuses for observability
Defensive patterns

Strategy: validation

Validate before calling

const data = await res.json();
if (data.error) throw new Error(data.error);

Type guard

function hasImageData(d) { return d != null && typeof d.image === 'string' && d.image.length > 0; }

Try / catch

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); }

Prevention

When it happens

Trigger: 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: ...}.

Common situations: 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.

Related errors


AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14). Data as JSON: /api/errors/54a3e123d3230112. Report an issue: GitHub.