odysseus-dev/odysseus · error · Error

statusText

Error message

statusText

What it means

Generic AI-editor tool runner: thrown when the POST to the configured image endpoint returns non-2xx. It prefers the JSON body's detail/error fields and falls back to res.statusText, which can be empty over HTTP/2.

Source

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

      const m = /\/api\/image\/([\w-]+)/.exec(endpoint || '');
      const type = m ? m[1].replace('upscale-ai', 'upscale').replace('remove-bg', 'rembg') : null;
      const sel = getSelectedAIEndpoint(type);
      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;

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Inspect the failing request's status and body in DevTools to separate auth/validation/upstream causes
  2. Confirm the endpoint path matches a route the current backend actually serves
  3. Shrink the exported PNG (flatten() output) if body size is the issue
  4. Use `HTTP ${res.status}` as the fallback message instead of bare statusText

Example fix

// before
let err = res.statusText;

// after
let err = `HTTP ${res.status}`;
Defensive patterns

Strategy: try-catch

Validate before calling

if (!endpoint || typeof endpoint !== 'string' || !endpoint.startsWith('/')) { uiModule.showToast('Invalid AI tool endpoint'); return; }

Try / catch

if (!res.ok) { let err = `HTTP ${res.status}`; try { const e = await res.json(); err = e.detail || e.error || err; } catch {} throw new Error(err); }

Prevention

When it happens

Trigger: Any editor AI tool routed through runAITool (expand, variations, etc.) failing at the HTTP level: 401 expired session, 422 payload validation, 413 oversized base64 image, 502 when the AI provider proxy is down, or an unknown endpoint path when endpoint is misconfigured.

Common situations: Server deployed without the AI routes mounted; endpoint variable built from stale config after a backend update; large canvas images exceeding proxy body limits; provider outage surfacing as 502 with an HTML body that fails JSON parsing.

Related errors


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