Hmbown/CodeWhale · error
${message}
Error message
${message} What it means
Generic failure of the browser client's api() wrapper: a fetch to the Runtime web API returned a non-OK HTTP status whose body text (or parsed JSON message) becomes the thrown error message. A 401 is upgraded to a friendly message telling the user their one-time browser session is no longer authenticated.
Solutions
- On the 401 variant, restart `codewhale web` and open the freshly printed one-time session URL in the same browser.
- Retry the action after confirming the Runtime process is still running and was not restarted since page load.
- If the message shows a 404/500, check Runtime logs for the failing endpoint and align the browser client and Runtime versions.
Example fix
// before: bookmarked stale session URL gets 401
fetch("/v1/providers")
// after: always launch via the fresh URL from `codewhale web`
// $ codewhale web
// Listening on http://127.0.0.1:PORT/?session=<one-time-token> Defensive patterns
Strategy: try-catch
Validate before calling
const res = await fetch('/v1/providers', { headers: authHeaders() });
if (res.status === 401) throw new Error('session expired — restart codewhale web');
if (!res.ok) throw new Error(`runtime api ${res.status}`); Type guard
function isAuthError(err) {
return err instanceof Error && err.message.includes('not authenticated');
} Try / catch
try {
data = await api('/v1/providers');
} catch (e) {
if (isAuthError(e)) showSessionExpiredDialog();
else showSnackbar(String(e.message));
} Prevention
- Always open the web UI from the URL printed by the current `codewhale web` run, never bookmarks.
- Treat any Runtime restart as invalidating all open browser sessions.
- Surface the HTTP status in the UI so users can distinguish auth vs server errors.
When it happens
Trigger: Any fetch from app.mjs via api() where response.ok is false — e.g. 401 after the one-time session token is consumed/expired, 404 on a stale endpoint, or 500 from a Runtime error.
Common situations: Bookmarking the web UI and reopening it later after the one-time session expired; sharing the URL with a second browser; Runtime restarted between page load and an API call; a version-mismatched Runtime missing an endpoint.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
- MCP server rejected the request with and refreshing the…
- MCP server rejected the request with ; the session is no…
- Preview points could not load.
- Reviewed plugin MCP authentication failed (provider details…
- Whale point asset is unavailable.
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/9b8286b1856615ed.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/runtime_web/app.mjs:955
}
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;
}
const groups = groupThreadSummaries(app.summaries);
if (groups.needsYou.length > 0) {
appendThreadGroup("Needs you", "needs-you", groups.needsYou);View on GitHub (pinned to 433685b202)