danielmiessler/Fabric · error · Error

HTTP error! status: ${response.status}

Error message

HTTP error! status: ${response.status}

What it means

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.

Source

Thrown at web/src/lib/api/base.ts:43

    }

    return { data: await response.json() as T };
  },

  get: <T>(endpoint: string) => api.fetch<T>(endpoint),
  post: <T>(endpoint: string, data: unknown) => api.fetch<T>(endpoint, { method: 'POST', body: JSON.stringify(data) }),
  put: <T>(endpoint: string, data?: unknown) => api.fetch<T>(endpoint, { method: 'PUT', body: data ? JSON.stringify(data) : undefined }),
  delete: <T>(endpoint: string) => api.fetch<T>(endpoint, { method: 'DELETE' }),

  stream: async function* (endpoint: string, data: unknown): AsyncGenerator<string> {
    const response = await fetch(`/api${endpoint}`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(data),
    });

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    const reader = response.body?.getReader();
    if (!reader) throw new Error('Response body is null');

    const decoder = new TextDecoder();
    while (true) {
      const { done, value } = await reader.read();
      yield decoder.decode(value);

      if (done) break;
    }
  }
};

export function createStorageAPI<T extends StorageEntity>(entityType: string) {
  return {
    async get(name: string): Promise<T> {

View on GitHub (pinned to 338b89cfe9)

Solutions

  1. Check which status code is embedded in the message, then reproduce the same POST with curl to see the server's error body
  2. If 404, verify the endpoint path and that the backend registers the matching /api route
  3. If 5xx, inspect backend logs for the panic/error behind the failed streaming call
  4. Improve the throw site to read response.text() before throwing so server detail is preserved

Example fix

// before
if (!response.ok) {
  throw new Error(`HTTP error! status: ${response.status}`);
}

// after
if (!response.ok) {
  const detail = await response.text().catch(() => '');
  throw new Error(`HTTP ${response.status} on ${endpoint}: ${detail || 'no body'}`);
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Verify the endpoint exists before streaming
const probe = await fetch(`/api${endpoint}`, { method: 'HEAD' }).catch(() => null);
if (!probe?.ok) {
  throw new Error(`Endpoint /api${endpoint} not ready (HEAD ${probe?.status ?? 'unreachable'})`);
}

Type guard

function isHttpStreamError(e: unknown, status?: number): boolean {
  return e instanceof Error && /HTTP error! status/.test(e.message)
    && (status === undefined || e.message.includes(String(status)));
}

Try / catch

try {
  for await (const chunk of api.stream('/chat', payload)) { /* ... */ }
} catch (e) {
  if (isHttpStreamError(e)) { /* surface status, stop retrying 4xx */ }
  else throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of danielmiessler/Fabric@338b89cfe9 (2026-08-15). Data as JSON: /api/errors/09f760a1199bd596. Report an issue: GitHub.