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

  1. 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.
  2. Restart the Electron app to reset the never-reset month counter once it approaches 15000.
  3. 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.
  4. 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

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


AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12). Data as JSON: /api/errors/816db97e0b607eff. Report an issue: GitHub.