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

  1. Confirm the codewhale web process is alive and re-open its session URL
  2. Check the server log at the same timestamp
  3. 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

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


AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16). Data as JSON: /api/errors/0332de9c9101c564. Report an issue: GitHub.