Hmbown/CodeWhale · error · Error
${response.status} ${response.statusText}
Error message
${response.status} ${response.statusText} What it means
In the desktop web app (runtime_web/app.mjs), a failed fetch becomes an Error with message `${status} ${statusText}`. That literal surfaces only when the error body is not JSON or carries neither error.message nor message - e.g. HTML error pages, empty bodies, or plain text from proxies.
Source
Thrown at crates/tui/src/runtime_web/app.mjs:714
}
const response = await fetch(path, {
...options,
headers,
credentials: "same-origin",
cache: "no-store",
});
if (!response.ok) {
let message = `${response.status} ${response.statusText}`.trim();
try {
const body = await response.json();
message = body?.error?.message || body?.message || message;
} catch (_error) {
// The status line is enough when the response is not JSON.
}
if (response.status === 401) {
message = "This browser session is not authenticated. Restart `codewhale web` to open a fresh one-time session.";
}
throw new Error(message);
}
if (response.status === 204) return null;
const contentType = response.headers.get("content-type") || "";
return contentType.includes("application/json") ? response.json() : response.text();
}
function renderThreadList() {
dom.threadList.replaceChildren();
if (app.summaries.length === 0) {
const empty = element("p", "thread-preview", "No matching threads");
empty.style.padding = "8px 10px";
dom.threadList.append(empty);
return;
}
for (const summary of app.summaries) {
const row = element("button", "thread-row");
row.type = "button";
row.dataset.threadId = summary.id;View on GitHub (pinned to 8880682c63)
Solutions
- Confirm the codewhale web process is alive and re-open its session URL
- Check the server log at the same timestamp
- If behind a proxy, bypass it or raise its timeouts
Example fix
// before
try {
const body = await response.json();
message = body?.error?.message || body?.message || message;
} catch (_error) {
// The status line is enough when the response is not JSON.
}
// after - clone first so the raw non-JSON body can still be logged
const rawClone = response.clone();
try {
const body = await response.json();
message = body?.error?.message || body?.message || message;
} catch (_error) {
console.warn('non-JSON error body:', await rawClone.text().catch(() => ''));
} Defensive patterns
Strategy: try-catch
Validate before calling
const res = await fetch(route, { credentials: 'same-origin', cache: 'no-store' });
if (!res.ok && !(res.headers.get('content-type') || '').includes('application/json')) {
console.error('non-JSON error from', route, res.status);
} Try / catch
try {
return await requestJson(route, options);
} catch (err) {
const m = /^(\d{3}) /.exec(err.message);
if (m && Number(m[1]) >= 500) { scheduleReconnect(); return null; }
showError(err.message);
throw err;
} Prevention
- Clone error responses before parsing so raw bodies remain loggable
- Keep the web app bundle and the API server version-locked
- Add liveness polling to detect a dead runtime before user actions fail
When it happens
Trigger: The runtime server returns 5xx with an HTML/empty body; a gateway answers 502/504; the requested route does not exist in the running build (404 with empty body).
Common situations: `codewhale web` crashed or is restarting while the browser keeps polling; version skew between app.mjs and the served API; corporate proxies rewriting responses.
Related errors
- ${detail || ("HTTP " + res.status)}
- expected application/x-www-form-urlencoded
- invalid Content-Length
- payload too large
- No active turn
AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16).
Data as JSON: /api/errors/0332de9c9101c564.
Report an issue: GitHub.