Hmbown/CodeWhale · error · ApiError
returned HTTP .
Error message
${label} returned HTTP ${statusCode}. What it means
The VS Code extension's API client routes every HTTP call through ensureOk, which throws an ApiError whenever the runtime answers a non-2xx status. The message embeds the route label and the numeric status; response-body detail is attached to the ApiError. It is a uniform guard, so any failed REST call to the Codewhale runtime surfaces here.
Solutions
- Check that the Codewhale runtime is running and reachable at the configured base URL and restart it if needed
- Read the ApiError's statusCode and detail body to identify the route-specific cause (404: thread missing; 409: turn conflict; 5xx: server fault)
- Refresh the thread list and retry against a valid thread key if you got 404
- Retry the failed operation after the runtime recovers — for 5xx/502/503 this is usually transient
Example fix
// before: calling with a stale thread key after runtime reset const detail = await api.getThreadDetail(oldKey); // 404 -> ApiError // after: resolve a current key first const threads = await api.listThreadSummaries(); const detail = threads.length ? await api.getThreadDetail(threads[0].key) : null;
Defensive patterns
Strategy: try-catch
Validate before calling
const healthy = await fetch(`${baseUrl}/health`).then(r => r.ok, () => false);
if (!healthy) throw new Error('Codewhale runtime unreachable - start it before using the extension'); Type guard
const isApiError = (e) => e instanceof ApiError || (e && typeof e.statusCode === 'number');
Try / catch
try {
await api.startTurn(threadKey, input);
} catch (e) {
if (isApiError(e)) {
if (e.statusCode === 404) { /* refresh thread list, pick a valid key */ }
else if (e.statusCode >= 500) { /* retry after runtime recovers */ }
else throw e;
} else throw e;
} Prevention
- Check runtime health before issuing API calls; surface a clear 'runtime not running' state in the UI
- Log statusCode and detail body for every ApiError to diagnose quickly
- Refresh thread keys after runtime restarts or data resets
- Retry idempotent GETs on 5xx with backoff; never blind-retry non-idempotent turns
When it happens
Trigger: Any of listThreadSummaries, getThreadDetail, createThread, startTurn, steerTurn, or interruptTurn receiving a response whose statusCode fails isOk (outside 2xx) — e.g. the runtime is down (connection refused yields its own failure path, but 404/409/500 bodies come through here), the thread key is unknown (404), or the runtime returns 500.
Common situations: Runtime server not running or restarted while the extension was open; stale thread keys after a data reset (404 on getThreadDetail); concurrent turns causing 409 on startTurn/steerTurn; auth or version mismatch producing 4xx; proxy/firewall returning an HTML error page with 502/503.
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
- Anthropic API error (HTTP )
- Cloud agent sandbox listing failed
- Cloudflare SQL request failed
- CNB pull request create failed
- Codewhale account request failed
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/5ef411aa9eddee91.
Report an issue: GitHub.
Appendix: source
Thrown at extensions/vscode/src/api.ts:423
/** Build the typed error for a non-2xx status, surfacing the runtime's own message. */
function apiError(statusCode: number, body: unknown, label: string): ApiError {
const detail = readErrorDetail(body);
if (statusCode === 0) {
return new ApiError(`${label} could not reach the runtime.`, 0, detail);
}
if (statusCode === 401) {
return new ApiError(`${label} requires the runtime token.`, 401, detail);
}
if (statusCode === 409) {
return new ConflictError(`${label} conflicts with the runtime's current state.`, detail);
}
return new ApiError(`${label} returned HTTP ${statusCode}.`, statusCode, detail);
}
/** Throw unless the runtime answered 2xx. Every route's status check runs through here. */
function ensureOk(response: RequestResult, label: string): void {
if (!isOk(response.statusCode)) {
throw apiError(response.statusCode, response.body, label);
}
}
async function requestJson(
url: string,
config: ApiConfig,
options: { method?: string; body?: string; timeoutMs: number },
): Promise<RequestResult> {
try {
return await new Promise<RequestResult>((resolve, reject) => {
const request = http.request(
url,
{
method: options.method ?? "GET",
timeout: options.timeoutMs,
headers: {
Accept: "application/json",
...(options.body ? { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(options.body) } : {}),View on GitHub (pinned to 433685b202)