CherryHQ/cherry-studio · warning · Error
Rate limit exceeded
Error message
Rate limit exceeded
What it means
Thrown by `checkRateLimit` in the Brave Search MCP server — a plain `Error`, not an McpError. It fires when `requestCount.second >= 1` (perSecond is 1) OR `requestCount.month >= 15000` (perMonth). Two important implementation details: (1) the per-second counter resets only if more than 1000ms elapsed since `lastReset`, so any two calls within the same second trip it; (2) the per-MONTH counter is initialized once at module load and NEVER reset anywhere in the code — it is a monotonic accumulator for the whole Electron process lifetime, so once it crosses 15000 every subsequent call fails permanently until the app restarts. Worse, a single `brave_local_search` fans out into multiple rate-checked calls (performLocalSearch itself, plus getPoisData and getDescriptionsData in parallel, and a performWebSearch fallback when there are no local results), so one logical local search can hit the perSecond=1 limit internally even with no external concurrency.
Source
Thrown at src/main/ai/mcp/servers/braveSearch.ts:84
const RATE_LIMIT = {
perSecond: 1,
perMonth: 15000
}
const requestCount = {
second: 0,
month: 0,
lastReset: Date.now()
}
function checkRateLimit() {
const now = Date.now()
if (now - requestCount.lastReset > 1000) {
requestCount.second = 0
requestCount.lastReset = now
}
if (requestCount.second >= RATE_LIMIT.perSecond || requestCount.month >= RATE_LIMIT.perMonth) {
throw new Error('Rate limit exceeded')
}
requestCount.second++
requestCount.month++
}
interface BraveWeb {
web?: {
results?: Array<{
title: string
description: string
url: string
language?: string
published?: string
rank?: number
}>
}
locations?: {
results?: Array<{View on GitHub (pinned to 726446b54c)
Solutions
- Serialize Brave Search calls with at least 1 second between them, and remember one local search counts as up to 3-4 internal rate checks.
- Restart the Electron app to reset the never-reset month counter once it approaches 15000.
- If you control the source, raise `RATE_LIMIT.perSecond` (braveSearch.ts:67) — a value of 1 makes local search reliably fail; the real Brave per-second allowance is higher.
- Add a month-counter reset (e.g. track the calendar month) — the current code never resets it, which is a latent bug.
Example fix
// before (local search self-trips the 1/sec limit)
await client.callTool('brave_local_search', { query: 'pizza near Central Park' })
// after (space calls >= 1s, and prefer web search to avoid internal fan-out)
await client.callTool('brave_web_search', { query: 'pizza near Central Park' }) Defensive patterns
Strategy: retry
Validate before calling
// Serialize Brave calls with >= 1s spacing; account for local-search fan-out.
const BRAVE_MIN_INTERVAL_MS = 1100 // local search fans out to ~3 internal checks
let lastBraveCall = 0
async function braveGate() {
const wait = BRAVE_MIN_INTERVAL_MS - (Date.now() - lastBraveCall)
if (wait > 0) await new Promise((r) => setTimeout(r, wait))
lastBraveCall = Date.now()
} Type guard
null
Try / catch
// braveSearch converts thrown errors to an isError result (braveSearch.ts:359-369).
async function braveSearchWithBackoff(client: Client, name: string, args: unknown, attempts = 3) {
for (let i = 0; i < attempts; i++) {
await braveGate()
const res = await client.callTool(name, args)
if (!res.isError) return res
if (!/Rate limit exceeded/.test(res.content[0].text)) return res
await new Promise((r) => setTimeout(r, 1100 * (i + 1)))
}
throw new Error('Brave rate limit persisted after retries')
} Prevention
- Prefer brave_web_search over brave_local_search — local search makes up to 3-4 internal rate-checked calls and reliably self-trips the perSecond=1 limit.
- Space all Brave calls at least 1 second apart and serialize them within a single client.
- Restart the Electron app periodically: the per-month counter never resets in-process and will eventually hard-fail every call.
- If you maintain this server, consider raising RATE_LIMIT.perSecond and adding a real month-boundary reset.
When it happens
Trigger: Two brave search calls within the same second; any `brave_local_search` that returns local results (it makes 3 checkRateLimit calls: web lookup + POI + descriptions, the latter two in parallel within the same second); 15000 cumulative calls across the process lifetime.
Common situations: Burst web searches from an agent loop; local search almost always self-tripping the per-second limit on its parallel POI/description fetches; a long-running desktop session accumulating toward the 15000 ceiling and then hard-failing until restart.
Related errors
- Failed to get gateway URL: HTTP ${response.status} - ${error
- Discord API error ${url}: HTTP ${response.status} - ${errorT
- OAuth authentication failed: ${oauthError instanceof Error ?
- Brave API error: ${response.status} ${response.statusText}\n
- Brave API error: ${webResponse.status} ${webResponse.statusT
AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12).
Data as JSON: /api/errors/816db97e0b607eff.
Report an issue: GitHub.