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

  1. Inspect error.cause in the caught ChatError — it holds the original TypeError with the real reason
  2. Confirm the backend is up and /api/chat is reachable from the browser (direct curl/GET)
  3. 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

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


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