{"record":{"id":"5cf691203584aa03","repo":"hoppscotch/hoppscotch","slug":"fetch-failed-errormessage","errorCode":null,"errorMessage":"Fetch failed: ${errorMessage}","messagePattern":"Fetch failed: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/hoppscotch-common/src/helpers/hopp-fetch.ts","lineNumber":55,"sourceCode":"\n    // Execute via interceptor\n    const execution = kernelInterceptor.execute(relayRequest)\n    const result = await execution.response\n\n    if (E.isLeft(result)) {\n      const error = result.left\n\n      const errorMessage =\n        typeof error === \"string\"\n          ? error\n          : typeof error === \"object\" &&\n              error !== null &&\n              \"humanMessage\" in error\n            ? typeof error.humanMessage.heading === \"function\"\n              ? error.humanMessage.heading(() => \"Unknown error\")\n              : \"Unknown error\"\n            : \"Unknown error\"\n      throw new Error(`Fetch failed: ${errorMessage}`)\n    }\n\n    // Convert RelayResponse to serializable Response-like object\n    // Native Response objects can't cross VM boundaries\n    return convertRelayResponseToSerializableResponse(result.right)\n  }\n}\n\n/**\n * Converts Fetch API request to RelayRequest format\n */\nasync function convertFetchToRelayRequest(\n  input: RequestInfo | URL,\n  init?: RequestInit\n): Promise<RelayRequest> {\n  const urlStr =\n    typeof input === \"string\"\n      ? input","sourceCodeStart":37,"sourceCodeEnd":73,"githubUrl":"https://github.com/hoppscotch/hoppscotch/blob/1acb8a3a7581e4db32ba0d529170c4669a2e1053/packages/hoppscotch-common/src/helpers/hopp-fetch.ts#L37-L73","documentation":"Top-level failure in hopp-fetch's fetch replacement: the kernelInterceptor.execute(relayRequest) returned an E.Left, so the request never produced a Response. The message is built by inspecting the left value — if it's a string it's used directly; if it's an object with a humanMessage.heading function, that's called; otherwise 'Unknown error'. This is the single chokepoint for every relay-backed fetch in the app.","triggerScenarios":"Any relay request (REST, GraphQL, backend call) fails at the interceptor layer: DNS failure, connection refused, TLS error, the agent interceptor rejecting (see error 78), a network policy block, or an interceptor-thrown structured error whose humanMessage is invoked.","commonSituations":"The Hoppscotch agent is not running (the agent interceptor throws 'Agent not running' which surfaces here); a CORS preflight fails; the target host is unreachable from the runtime; a corporate proxy blocks the request; the request was cancelled and the interceptor returns Left('cancelled').","solutions":["If using the agent interceptor, confirm the agent is running and authenticated (see error 78).","Read the errorMessage portion of the thrown string to identify the underlying cause (network, agent, CORS).","For CORS, route the request through the agent/proxy rather than the browser directly.","If the left value is a structured error with humanMessage, surface error.humanMessage.body or .heading to the user instead of 'Unknown error'."],"exampleFix":"// before\nconst errorMessage =\n  typeof error === \"string\"\n    ? error\n    : typeof error === \"object\" && error !== null && \"humanMessage\" in error\n      ? typeof error.humanMessage.heading === \"function\"\n        ? error.humanMessage.heading(() => \"Unknown error\")\n        : \"Unknown error\"\n      : \"Unknown error\"\nthrow new Error(`Fetch failed: ${errorMessage}`)\n\n// after\nconst errorMessage =\n  typeof error === \"string\"\n    ? error\n    : typeof error === \"object\" && error !== null && \"humanMessage\" in error\n      ? [error.humanMessage.heading?.(() => \"Unknown error\"), error.humanMessage.body?.()].filter(Boolean).join(\" — \")\n      : JSON.stringify(error)\nthrow new Error(`Fetch failed: ${errorMessage}`)","handlingStrategy":"try-catch","validationCode":"// Pre-flight reachability check (best-effort) before the real fetch\nconst canReach = async (url: string): Promise<boolean> => {\n  try {\n    await fetch(url, { method: 'HEAD', mode: 'no-cors' })\n    return true\n  } catch { return false }\n}","typeGuard":"const isStructuredFetchError = (e: unknown): e is { humanMessage: { heading: Function; body?: () => string } } =>\n  typeof e === 'object' && e !== null && 'humanMessage' in e","tryCatchPattern":"try {\n  return await hoppFetch(url, init)\n} catch (e) {\n  const m = e.message\n  if (m.startsWith('Fetch failed:')) {\n    const detail = m.slice('Fetch failed:'.length).trim()\n    if (detail === 'Agent not running') await promptStartAgent()\n    else toast.error(detail)\n  }\n  throw e\n}","preventionTips":["Wrap every hoppFetch call in try/catch and parse the 'Fetch failed:' prefix.","Surface structured humanMessage.heading + body when present.","Verify agent/proxy availability before issuing requests through the interceptor."],"tags":["fetch","network","relay","interceptor","error-handling"],"backgroundTag":null,"analyzedSha":"1acb8a3a7581e4db32ba0d529170c4669a2e1053","analyzedAt":"2026-08-12T11:34:52.648Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}