{"record":{"id":"816c93031c5dfb06","repo":"CherryHQ/cherry-studio","slug":"failed-to-fetch-url-unknown-error","errorCode":null,"errorMessage":"Failed to fetch ${url}: Unknown error","messagePattern":"Failed to fetch (.+?): Unknown error","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/main/ai/mcp/servers/fetch.ts","lineNumber":39,"sourceCode":"\n  if (!resolvedHeaders.has('User-Agent')) {\n    resolvedHeaders.set('User-Agent', DEFAULT_USER_AGENT)\n  }\n\n  return resolvedHeaders\n}\n\nexport class Fetcher {\n  private static async _fetchText({ url, headers }: RequestPayload): Promise<string> {\n    try {\n      // The URL is model-supplied and this tool is auto-callable, so direct\n      // main-process fetches must bind the connection to validated DNS results.\n      return await fetchRemoteText(url, { headers: buildHeaders(headers), maxRedirects: 5 })\n    } catch (e: unknown) {\n      if (e instanceof Error) {\n        throw new Error(`Failed to fetch ${url}: ${e.message}`)\n      } else {\n        throw new Error(`Failed to fetch ${url}: Unknown error`)\n      }\n    }\n  }\n\n  static async html(requestPayload: RequestPayload) {\n    try {\n      const html = await this._fetchText(requestPayload)\n      return { content: [{ type: 'text', text: html }], isError: false }\n    } catch (error) {\n      return {\n        content: [{ type: 'text', text: (error as Error).message }],\n        isError: true\n      }\n    }\n  }\n\n  static async json(requestPayload: RequestPayload) {\n    try {","sourceCodeStart":21,"sourceCodeEnd":57,"githubUrl":"https://github.com/CherryHQ/cherry-studio/blob/726446b54cd69ffe51a276638672f6d95ca0768c/src/main/ai/mcp/servers/fetch.ts#L21-L57","documentation":"The defensive fallback branch of _fetchText's catch: fetchRemoteText rejected with a non-Error value (a string, number, null, undefined, or a plain object without Error.prototype). It exists because a bare `throw 'string'` in a dependency would otherwise produce an unhelpful '[object Object]' or empty message. Rare in practice — almost every Node.js and undici rejection is an Error instance — but it guarantees the tool result always carries a human-readable reason.","triggerScenarios":"A dependency or interceptor along the fetch path throws a non-Error value, e.g. a third-party middleware doing `throw 'network down'`, a Promise reject with `null`, or a custom agent that rejects with a status code number. Also reachable if fetchRemoteText itself has a code path that does `throw someString`.","commonSituations":"Patched or monkey-patched fetch implementations in test harnesses; older libraries that reject with strings rather than Error objects; bugs in interceptors (e.g. a proxy module) that reject with the HTTP status number.","solutions":["Inspect the full stack/context around the call — 'Unknown error' means the cause was not an Error, so the original value was swallowed; add logging in _fetchText to capture the raw `e` value before it is lost.","Search the dependency chain (fetchRemoteText and anything it calls) for a non-Error throw or reject and convert it to an Error.","If reproducing in tests, ensure mocks reject with `new Error(...)` rather than bare strings."],"exampleFix":"// before\n} else {\n  throw new Error(`Failed to fetch ${url}: Unknown error`)\n}\n\n// after — preserve the raw value so it is diagnosable\n} else {\n  logger.error('fetchRemoteText rejected with non-Error', { url, value: e })\n  throw new Error(`Failed to fetch ${url}: ${typeof e === 'string' ? e : 'Unknown error'}`)\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"// Distinguish Error from non-Error rejections so the 'Unknown error' branch is reachable.\nfunction isErrorLike(e: unknown): e is Error {\n  return e instanceof Error || (typeof e === 'object' && e !== null && 'message' in e && typeof (e as any).message === 'string')\n}","tryCatchPattern":"// Catch non-Error rejections and preserve their value for diagnosis.\ntry {\n  return await fetchRemoteText(url, opts)\n} catch (e: unknown) {\n  if (e instanceof Error) throw new Error(`Failed to fetch ${url}: ${e.message}`)\n  console.error('non-Error rejection from fetchRemoteText', e)\n  throw new Error(`Failed to fetch ${url}: ${typeof e === 'string' ? e : 'Unknown error'}`)\n}","preventionTips":["Ensure all dependencies reject with Error instances, not bare strings/numbers.","In test mocks, always reject with `new Error(...)` rather than `throw 'msg'`.","Log the raw rejection value before rethrowing so 'Unknown error' is diagnosable.","Avoid interceptors that convert errors to plain objects."],"tags":["error-handling","fetch","defensive"],"backgroundTag":null,"analyzedSha":"726446b54cd69ffe51a276638672f6d95ca0768c","analyzedAt":"2026-08-12T17:30:37.448Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}