{"record":{"id":"fc97f86d40611ae5","repo":"CherryHQ/cherry-studio","slug":"brave-api-error-webresponse-status-webrespon","errorCode":null,"errorMessage":"Brave API error: ${webResponse.status} ${webResponse.statusText}\\n${await webResponse.text()}","messagePattern":"Brave API error: (.+?) (.+?)\\\\n(.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/main/ai/mcp/servers/braveSearch.ts","lineNumber":206,"sourceCode":"async function performLocalSearch(apiKey: string, query: string, count: number = 5) {\n  checkRateLimit()\n  // Initial search to get location IDs\n  const webUrl = new URL('https://api.search.brave.com/res/v1/web/search')\n  webUrl.searchParams.set('q', query)\n  webUrl.searchParams.set('search_lang', 'en')\n  webUrl.searchParams.set('result_filter', 'locations')\n  webUrl.searchParams.set('count', Math.min(count, 20).toString())\n\n  const webResponse = await net.fetch(webUrl.toString(), {\n    headers: {\n      Accept: 'application/json',\n      'Accept-Encoding': 'gzip',\n      'X-Subscription-Token': apiKey\n    }\n  })\n\n  if (!webResponse.ok) {\n    throw new Error(`Brave API error: ${webResponse.status} ${webResponse.statusText}\\n${await webResponse.text()}`)\n  }\n\n  const webData = (await webResponse.json()) as BraveWeb\n  const locationIds =\n    webData.locations?.results?.filter((r): r is { id: string; title?: string } => r.id != null).map((r) => r.id) || []\n\n  if (locationIds.length === 0) {\n    return performWebSearch(apiKey, query, count) // Fallback to web search\n  }\n\n  // Get POI details and descriptions in parallel\n  const [poisData, descriptionsData] = await Promise.all([\n    getPoisData(apiKey, locationIds),\n    getDescriptionsData(apiKey, locationIds)\n  ])\n\n  return formatLocalResults(poisData, descriptionsData)\n}","sourceCodeStart":188,"sourceCodeEnd":224,"githubUrl":"https://github.com/CherryHQ/cherry-studio/blob/726446b54cd69ffe51a276638672f6d95ca0768c/src/main/ai/mcp/servers/braveSearch.ts#L188-L224","documentation":"Thrown in the first leg of performLocalSearch: it queries Brave's web search with result_filter=locations to discover location IDs. If that initial HTTP response is not OK, the error propagates immediately. Note this is distinct from the empty-results fallback (performWebSearch) which only triggers when the call succeeds but yields zero locations.","triggerScenarios":"Calling brave_local_search when the locations-filtered web request fails: invalid X-Subscription-Token (401), quota/rate limit (429), or Brave backend error (5xx) on the locations probe specifically.","commonSituations":"Same key/quota causes as web search, but surfaced through the local-search path; users assume local search is broken when the real cause is the shared subscription token or rate budget. The local-search tool does NOT fall back to web search on HTTP errors — only on empty location lists.","solutions":["Read the status code in the message: 401 -> fix BRAVE_API_KEY; 429 -> back off; 5xx -> retry.","Confirm the same Brave key works for a plain brave_web_search call (isolates whether the issue is key/quota vs. the local endpoint).","If the locations endpoint is persistently failing, the tool has no graceful degradation — surface the error to the agent and retry later.","Reduce request frequency; checkRateLimit() caps at 1/sec locally but Brave may 429 sooner on shared keys."],"exampleFix":"// before — HTTP error on the locations probe propagates with no fallback\nif (!webResponse.ok) {\n  throw new Error(`Brave API error: ${webResponse.status} ...`)\n}\n\n// after — fall back to web search on transient location-probe failures\nif (!webResponse.ok) {\n  if (webResponse.status === 429 || webResponse.status >= 500) {\n    return performWebSearch(apiKey, query, count) // degrade gracefully\n  }\n  throw new Error(`Brave API error: ${webResponse.status} ${webResponse.statusText}\\n${await webResponse.text()}`)\n}","handlingStrategy":"try-catch","validationCode":"function validateLocalArgs(args) {\n  if (typeof args.query !== 'string' || !args.query.trim()) throw new Error('query required')\n  if (args.query.length > 400) throw new Error('query must be <= 400 chars')\n  const c = args.count ?? 5\n  if (typeof c !== 'number' || c < 1 || c > 20) throw new Error('count must be 1-20')\n  return { query: args.query.trim(), count: Math.min(c, 20) }\n}","typeGuard":"function isLocalArgs(a): a is { query: string; count?: number } {\n  return typeof a === 'object' && a !== null && typeof a.query === 'string' && a.query.trim().length > 0\n}","tryCatchPattern":"// Local search: fall back to plain web search if the locations probe fails transiently\nasync function localSearchSafe(apiKey, query, count) {\n  try {\n    return await performLocalSearch(apiKey, query, count)\n  } catch (e) {\n    const status = parseInt((String(e.message || '').match(/Brave API error: (\\d+)/) || [])[1] || '0', 10)\n    if (status === 429 || status >= 500) {\n      return await performWebSearch(apiKey, query, count)\n    }\n    throw e\n  }\n}","preventionTips":["Share the same validated BRAVE_API_KEY across both web and local paths.","Treat the locations endpoint as best-effort — degrade to web search on transient failures.","Serialize local-search calls to avoid bursting the shared quota."],"tags":["network","api","brave-search","http","local-search"],"backgroundTag":null,"analyzedSha":"726446b54cd69ffe51a276638672f6d95ca0768c","analyzedAt":"2026-08-12T17:30:37.448Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}