{"record":{"id":"302de58fdaead639","repo":"danielmiessler/Fabric","slug":"fetch-error","errorCode":"FETCH_ERROR","errorMessage":"Failed to fetch chat stream","messagePattern":"Failed to fetch chat stream","errorType":"exception","errorClass":"ChatError","httpStatus":null,"severity":"error","filePath":"web/src/lib/services/ChatService.ts","lineNumber":85,"sourceCode":"\t\t\t});\n\n\t\t\tif (!response.ok) {\n\t\t\t\tthrow new ChatError(\n\t\t\t\t\t`HTTP error! status: ${response.status}`,\n\t\t\t\t\t\"HTTP_ERROR\",\n\t\t\t\t\t{ status: response.status },\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst reader = response.body?.getReader();\n\t\t\tif (!reader) {\n\t\t\t\tthrow new ChatError(\"Response body is null\", \"NULL_RESPONSE\");\n\t\t\t}\n\n\t\t\treturn this.createMessageStream(reader);\n\t\t} catch (error) {\n\t\t\tif (error instanceof ChatError) throw error;\n\t\t\tthrow new ChatError(\"Failed to fetch chat stream\", \"FETCH_ERROR\", error);\n\t\t}\n\t}\n\n\t/**\n\t * Clean up pattern output for display. Should only be called on complete/accumulated content,\n\t * never on individual streaming tokens (which have leading spaces as word separators).\n\t */\n\tpublic cleanPatternOutput(content: string): string {\n\t\t// Remove markdown fence if present\n\t\tlet cleaned = content.replace(/^```markdown\\n/, \"\");\n\t\tcleaned = cleaned.replace(/\\n```$/, \"\");\n\n\t\t// Existing cleaning\n\t\tcleaned = cleaned.replace(/^# OUTPUT\\s*\\n/, \"\");\n\t\tcleaned = cleaned.replace(/^\\s*\\n/, \"\");\n\t\tcleaned = cleaned.replace(/\\n\\s*$/, \"\");\n\t\tcleaned = cleaned.replace(/^#\\s+([A-Z]+):/gm, \"$1:\");\n\t\tcleaned = cleaned.replace(/^#\\s+([A-Z]+)\\s*$/gm, \"$1\");","sourceCodeStart":67,"sourceCodeEnd":103,"githubUrl":"https://github.com/danielmiessler/Fabric/blob/338b89cfe97ab2d12ce30ce8b5449857a841366d/web/src/lib/services/ChatService.ts#L67-L103","documentation":"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.","triggerScenarios":"fetch() rejecting: backend unreachable (ECONNREFUSED), DNS failure, CORS preflight blocked for /api/chat, TLS certificate error, or AbortController timeout firing during connection.","commonSituations":"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.","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"],"exampleFix":"// before\n} catch (error) {\n  if (error instanceof ChatError) throw error;\n  throw new ChatError('Failed to fetch chat stream', 'FETCH_ERROR', error);\n}\n\n// after — keep cause visible in the message for diagnostics\n} catch (error) {\n  if (error instanceof ChatError) throw error;\n  const reason = error instanceof Error ? error.message : String(error);\n  throw new ChatError(`Failed to fetch chat stream: ${reason}`, 'FETCH_ERROR', error);\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":"function isFetchTransportError(e: unknown): e is ChatError & { code: 'FETCH_ERROR' } {\n  return e instanceof ChatError && e.code === 'FETCH_ERROR';\n}","tryCatchPattern":"let lastErr: unknown;\nfor (let i = 0; i < 2; i++) {\n  try { stream = await chatService.sendMessage(request); break; }\n  catch (e) {\n    if (!isFetchTransportError(e)) throw e;\n    lastErr = e;\n    await sleep(1000);\n  }\n}\nif (!stream) throw lastErr;","preventionTips":["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)"],"tags":["network","chat","cors","fetch"],"backgroundTag":null,"analyzedSha":"338b89cfe97ab2d12ce30ce8b5449857a841366d","analyzedAt":"2026-08-15T11:38:51.759Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}