{"record":{"id":"5ef411aa9eddee91","repo":"Hmbown/CodeWhale","slug":"label-returned-http-statuscode","errorCode":null,"errorMessage":"${label} returned HTTP ${statusCode}.","messagePattern":"(.+?) returned HTTP (.+?)\\.","errorType":"http","errorClass":"ApiError","httpStatus":null,"severity":"error","filePath":"extensions/vscode/src/api.ts","lineNumber":423,"sourceCode":"/** Build the typed error for a non-2xx status, surfacing the runtime's own message. */\nfunction apiError(statusCode: number, body: unknown, label: string): ApiError {\n  const detail = readErrorDetail(body);\n  if (statusCode === 0) {\n    return new ApiError(`${label} could not reach the runtime.`, 0, detail);\n  }\n  if (statusCode === 401) {\n    return new ApiError(`${label} requires the runtime token.`, 401, detail);\n  }\n  if (statusCode === 409) {\n    return new ConflictError(`${label} conflicts with the runtime's current state.`, detail);\n  }\n  return new ApiError(`${label} returned HTTP ${statusCode}.`, statusCode, detail);\n}\n\n/** Throw unless the runtime answered 2xx. Every route's status check runs through here. */\nfunction ensureOk(response: RequestResult, label: string): void {\n  if (!isOk(response.statusCode)) {\n    throw apiError(response.statusCode, response.body, label);\n  }\n}\n\nasync function requestJson(\n  url: string,\n  config: ApiConfig,\n  options: { method?: string; body?: string; timeoutMs: number },\n): Promise<RequestResult> {\n  try {\n    return await new Promise<RequestResult>((resolve, reject) => {\n      const request = http.request(\n        url,\n        {\n          method: options.method ?? \"GET\",\n          timeout: options.timeoutMs,\n          headers: {\n            Accept: \"application/json\",\n            ...(options.body ? { \"Content-Type\": \"application/json\", \"Content-Length\": Buffer.byteLength(options.body) } : {}),","sourceCodeStart":405,"sourceCodeEnd":441,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/extensions/vscode/src/api.ts#L405-L441","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before: calling with a stale thread key after runtime reset\nconst detail = await api.getThreadDetail(oldKey); // 404 -> ApiError\n\n// after: resolve a current key first\nconst threads = await api.listThreadSummaries();\nconst detail = threads.length ? await api.getThreadDetail(threads[0].key) : null;","handlingStrategy":"try-catch","validationCode":"const healthy = await fetch(`${baseUrl}/health`).then(r => r.ok, () => false);\nif (!healthy) throw new Error('Codewhale runtime unreachable - start it before using the extension');","typeGuard":"const isApiError = (e) => e instanceof ApiError || (e && typeof e.statusCode === 'number');","tryCatchPattern":"try {\n  await api.startTurn(threadKey, input);\n} catch (e) {\n  if (isApiError(e)) {\n    if (e.statusCode === 404) { /* refresh thread list, pick a valid key */ }\n    else if (e.statusCode >= 500) { /* retry after runtime recovers */ }\n    else throw e;\n  } else throw e;\n}","preventionTips":["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"],"tags":["http","vscode","api"],"backgroundTag":"http-error-response","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}