CherryHQ/cherry-studio · error · Error
Brave API error: ${webResponse.status} ${webResponse.statusT
Error message
Brave API error: ${webResponse.status} ${webResponse.statusText}\n${await webResponse.text()} What it means
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.
Source
Thrown at src/main/ai/mcp/servers/braveSearch.ts:206
async function performLocalSearch(apiKey: string, query: string, count: number = 5) {
checkRateLimit()
// Initial search to get location IDs
const webUrl = new URL('https://api.search.brave.com/res/v1/web/search')
webUrl.searchParams.set('q', query)
webUrl.searchParams.set('search_lang', 'en')
webUrl.searchParams.set('result_filter', 'locations')
webUrl.searchParams.set('count', Math.min(count, 20).toString())
const webResponse = await net.fetch(webUrl.toString(), {
headers: {
Accept: 'application/json',
'Accept-Encoding': 'gzip',
'X-Subscription-Token': apiKey
}
})
if (!webResponse.ok) {
throw new Error(`Brave API error: ${webResponse.status} ${webResponse.statusText}\n${await webResponse.text()}`)
}
const webData = (await webResponse.json()) as BraveWeb
const locationIds =
webData.locations?.results?.filter((r): r is { id: string; title?: string } => r.id != null).map((r) => r.id) || []
if (locationIds.length === 0) {
return performWebSearch(apiKey, query, count) // Fallback to web search
}
// Get POI details and descriptions in parallel
const [poisData, descriptionsData] = await Promise.all([
getPoisData(apiKey, locationIds),
getDescriptionsData(apiKey, locationIds)
])
return formatLocalResults(poisData, descriptionsData)
}View on GitHub (pinned to 726446b54c)
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.
Example fix
// before — HTTP error on the locations probe propagates with no fallback
if (!webResponse.ok) {
throw new Error(`Brave API error: ${webResponse.status} ...`)
}
// after — fall back to web search on transient location-probe failures
if (!webResponse.ok) {
if (webResponse.status === 429 || webResponse.status >= 500) {
return performWebSearch(apiKey, query, count) // degrade gracefully
}
throw new Error(`Brave API error: ${webResponse.status} ${webResponse.statusText}\n${await webResponse.text()}`)
} Defensive patterns
Strategy: try-catch
Validate before calling
function validateLocalArgs(args) {
if (typeof args.query !== 'string' || !args.query.trim()) throw new Error('query required')
if (args.query.length > 400) throw new Error('query must be <= 400 chars')
const c = args.count ?? 5
if (typeof c !== 'number' || c < 1 || c > 20) throw new Error('count must be 1-20')
return { query: args.query.trim(), count: Math.min(c, 20) }
} Type guard
function isLocalArgs(a): a is { query: string; count?: number } {
return typeof a === 'object' && a !== null && typeof a.query === 'string' && a.query.trim().length > 0
} Try / catch
// Local search: fall back to plain web search if the locations probe fails transiently
async function localSearchSafe(apiKey, query, count) {
try {
return await performLocalSearch(apiKey, query, count)
} catch (e) {
const status = parseInt((String(e.message || '').match(/Brave API error: (\d+)/) || [])[1] || '0', 10)
if (status === 429 || status >= 500) {
return await performWebSearch(apiKey, query, count)
}
throw e
}
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Brave API error: ${response.status} ${response.statusText}\n
- ModelScope API error: ${response.status} - ${errorText}
- PPIO API error: ${response.status} - ${errorText}
- Discord API error ${url}: HTTP ${response.status} - ${errorT
- Failed to get access token: HTTP ${response.status}
AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12).
Data as JSON: /api/errors/fc97f86d40611ae5.
Report an issue: GitHub.