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
- Inspect the failing request's status and body in DevTools to separate auth/validation/upstream causes
- Confirm the endpoint path matches a route the current backend actually serves
- Shrink the exported PNG (flatten() output) if body size is the issue
- 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
- Centralize the ok/detail/error/status fallback in one helper reused by all AI tools
- Include status codes in every fallback message
- Version-pin frontend endpoints with the backend routes table
- Test tool endpoints after backend upgrades before users hit them
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
- statusText
- Server returned ${res.status}
- statusText
- await res.text()
- data?.error || data?.detail || `HTTP ${res.status}`
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/9ea21d07fc79e7e1.
Report an issue: GitHub.