{"record":{"id":"09f760a1199bd596","repo":"danielmiessler/Fabric","slug":"http-error-status-response-status","errorCode":null,"errorMessage":"HTTP error! status: ${response.status}","messagePattern":"HTTP error! status: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web/src/lib/api/base.ts","lineNumber":43,"sourceCode":"    }\n\n    return { data: await response.json() as T };\n  },\n\n  get: <T>(endpoint: string) => api.fetch<T>(endpoint),\n  post: <T>(endpoint: string, data: unknown) => api.fetch<T>(endpoint, { method: 'POST', body: JSON.stringify(data) }),\n  put: <T>(endpoint: string, data?: unknown) => api.fetch<T>(endpoint, { method: 'PUT', body: data ? JSON.stringify(data) : undefined }),\n  delete: <T>(endpoint: string) => api.fetch<T>(endpoint, { method: 'DELETE' }),\n\n  stream: async function* (endpoint: string, data: unknown): AsyncGenerator<string> {\n    const response = await fetch(`/api${endpoint}`, {\n      method: 'POST',\n      headers: { 'Content-Type': 'application/json' },\n      body: JSON.stringify(data),\n    });\n\n    if (!response.ok) {\n      throw new Error(`HTTP error! status: ${response.status}`);\n    }\n\n    const reader = response.body?.getReader();\n    if (!reader) throw new Error('Response body is null');\n\n    const decoder = new TextDecoder();\n    while (true) {\n      const { done, value } = await reader.read();\n      yield decoder.decode(value);\n\n      if (done) break;\n    }\n  }\n};\n\nexport function createStorageAPI<T extends StorageEntity>(entityType: string) {\n  return {\n    async get(name: string): Promise<T> {","sourceCodeStart":25,"sourceCodeEnd":61,"githubUrl":"https://github.com/danielmiessler/Fabric/blob/338b89cfe97ab2d12ce30ce8b5449857a841366d/web/src/lib/api/base.ts#L25-L61","documentation":"Thrown by the generic api.stream() helper in web/src/lib/api/base.ts when a streaming POST to /api<endpoint> returns a non-2xx status. The helper only checks response.ok before trying to acquire a reader, so any HTTP-level failure (404, 500, 503) surfaces as this generic error with only the numeric status attached. It does not attempt to read the response body, so any server error detail is lost.","triggerScenarios":"Any call to api.stream(endpoint, data) where the server replies non-OK: unknown route under /api (404), backend down or proxy misrouted (502/503), malformed JSON body causing a 400, or an auth/session expiry returning 401.","commonSituations":"Dev server proxy not forwarding the /api prefix to the backend; backend handler panicked mid-request; sending a payload the endpoint rejects; running the frontend against a stale backend that lacks the route.","solutions":["Check which status code is embedded in the message, then reproduce the same POST with curl to see the server's error body","If 404, verify the endpoint path and that the backend registers the matching /api route","If 5xx, inspect backend logs for the panic/error behind the failed streaming call","Improve the throw site to read response.text() before throwing so server detail is preserved"],"exampleFix":"// before\nif (!response.ok) {\n  throw new Error(`HTTP error! status: ${response.status}`);\n}\n\n// after\nif (!response.ok) {\n  const detail = await response.text().catch(() => '');\n  throw new Error(`HTTP ${response.status} on ${endpoint}: ${detail || 'no body'}`);\n}","handlingStrategy":"try-catch","validationCode":"// Verify the endpoint exists before streaming\nconst probe = await fetch(`/api${endpoint}`, { method: 'HEAD' }).catch(() => null);\nif (!probe?.ok) {\n  throw new Error(`Endpoint /api${endpoint} not ready (HEAD ${probe?.status ?? 'unreachable'})`);\n}","typeGuard":"function isHttpStreamError(e: unknown, status?: number): boolean {\n  return e instanceof Error && /HTTP error! status/.test(e.message)\n    && (status === undefined || e.message.includes(String(status)));\n}","tryCatchPattern":"try {\n  for await (const chunk of api.stream('/chat', payload)) { /* ... */ }\n} catch (e) {\n  if (isHttpStreamError(e)) { /* surface status, stop retrying 4xx */ }\n  else throw e;\n}","preventionTips":["Health-check the backend before starting a stream","Include the endpoint name in thrown errors at the call site for fast triage","Never retry 4xx statuses; only retry 5xx with backoff"],"tags":["http","streaming","network","api-client"],"backgroundTag":null,"analyzedSha":"338b89cfe97ab2d12ce30ce8b5449857a841366d","analyzedAt":"2026-08-15T11:38:51.759Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}