danielmiessler/Fabric · error · ChatError
FETCH_ERROR
FETCH_ERROR
Error message
Failed to fetch chat stream
What it means
The catch-all in ChatService.sendMessage wraps any non-ChatError failure as ChatError('Failed to fetch chat stream', 'FETCH_ERROR', cause). The original error is preserved in the cause field. This is the network/transport tier: the request never got a usable HTTP response at all.
Source
Thrown at web/src/lib/services/ChatService.ts:85
});
if (!response.ok) {
throw new ChatError(
`HTTP error! status: ${response.status}`,
"HTTP_ERROR",
{ status: response.status },
);
}
const reader = response.body?.getReader();
if (!reader) {
throw new ChatError("Response body is null", "NULL_RESPONSE");
}
return this.createMessageStream(reader);
} catch (error) {
if (error instanceof ChatError) throw error;
throw new ChatError("Failed to fetch chat stream", "FETCH_ERROR", error);
}
}
/**
* Clean up pattern output for display. Should only be called on complete/accumulated content,
* never on individual streaming tokens (which have leading spaces as word separators).
*/
public cleanPatternOutput(content: string): string {
// Remove markdown fence if present
let cleaned = content.replace(/^```markdown\n/, "");
cleaned = cleaned.replace(/\n```$/, "");
// Existing cleaning
cleaned = cleaned.replace(/^# OUTPUT\s*\n/, "");
cleaned = cleaned.replace(/^\s*\n/, "");
cleaned = cleaned.replace(/\n\s*$/, "");
cleaned = cleaned.replace(/^#\s+([A-Z]+):/gm, "$1:");
cleaned = cleaned.replace(/^#\s+([A-Z]+)\s*$/gm, "$1");View on GitHub (pinned to 338b89cfe9)
Solutions
- Inspect error.cause in the caught ChatError — it holds the original TypeError with the real reason
- Confirm the backend is up and /api/chat is reachable from the browser (direct curl/GET)
- Fix the proxy/CORS config so /api routes to the backend with the right scheme
Example fix
// before
} catch (error) {
if (error instanceof ChatError) throw error;
throw new ChatError('Failed to fetch chat stream', 'FETCH_ERROR', error);
}
// after — keep cause visible in the message for diagnostics
} catch (error) {
if (error instanceof ChatError) throw error;
const reason = error instanceof Error ? error.message : String(error);
throw new ChatError(`Failed to fetch chat stream: ${reason}`, 'FETCH_ERROR', error);
} Defensive patterns
Strategy: retry
Type guard
function isFetchTransportError(e: unknown): e is ChatError & { code: 'FETCH_ERROR' } {
return e instanceof ChatError && e.code === 'FETCH_ERROR';
} Try / catch
let lastErr: unknown;
for (let i = 0; i < 2; i++) {
try { stream = await chatService.sendMessage(request); break; }
catch (e) {
if (!isFetchTransportError(e)) throw e;
lastErr = e;
await sleep(1000);
}
}
if (!stream) throw lastErr; Prevention
- Check backend reachability before the first message
- Always inspect error.cause — it distinguishes CORS, DNS, and abort failures
- Retry only FETCH_ERROR (transport), never HTTP_ERROR (server said no)
When it happens
Trigger: fetch() rejecting: backend unreachable (ECONNREFUSED), DNS failure, CORS preflight blocked for /api/chat, TLS certificate error, or AbortController timeout firing during connection.
Common situations: Dev backend not running while the frontend is; proxy misconfigured so /api is not forwarded; mixed-content (https page calling http backend); ad-blocker or extension killing the request.
Related errors
- NULL_RESPONSE
- HTTP error! status: ${response.status}
- Response body is null
- HTTP_ERROR
- STREAM_CONTENT_ERROR
AI-assisted analysis of danielmiessler/Fabric@338b89cfe9 (2026-08-15).
Data as JSON: /api/errors/302de58fdaead639.
Report an issue: GitHub.