{"record":{"id":"a3ca7517364722e9","repo":"jackwener/OpenCLI","slug":"label-returned-http-429-rate-limited-a3ca75","errorCode":null,"errorMessage":"${label} returned HTTP 429 (rate limited)","messagePattern":"(.+?) returned HTTP 429 \\(rate limited\\)","errorType":"http","errorClass":"CommandExecutionError","httpStatus":429,"severity":"warning","filePath":"clis/homebrew/utils.js","lineNumber":75,"sourceCode":"    return s;\n}\n\nexport async function brewFetch(url, label) {\n    let resp;\n    try {\n        resp = await fetch(url, { headers: { 'user-agent': UA, accept: 'application/json' } });\n    }\n    catch (err) {\n        throw new CommandExecutionError(\n            `${label} request failed: ${err?.message ?? err}`,\n            'Check that formulae.brew.sh is reachable from this network.',\n        );\n    }\n    if (resp.status === 404) {\n        throw new EmptyResultError(label, `Homebrew API returned 404 for ${url}.`);\n    }\n    if (resp.status === 429) {\n        throw new CommandExecutionError(\n            `${label} returned HTTP 429 (rate limited)`,\n            'Homebrew throttles bursts; wait a few seconds and retry.',\n        );\n    }\n    if (!resp.ok) {\n        throw new CommandExecutionError(`${label} returned HTTP ${resp.status}`);\n    }\n    let body;\n    try {\n        body = await resp.json();\n    }\n    catch (err) {\n        throw new CommandExecutionError(`${label} returned malformed JSON: ${err?.message ?? err}`);\n    }\n    return body;\n}\n\n/** Coerce a count value (which Homebrew analytics serves as `\"139,972\"`) to a plain number. */","sourceCodeStart":57,"sourceCodeEnd":93,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/homebrew/utils.js#L57-L93","documentation":"The public formulae.brew.sh API rate-limits bursts of requests. When brewFetch receives HTTP 429 it throws a CommandExecutionError telling the caller the request was throttled and to wait a few seconds before retrying. The API is served as static files from GitHub Pages, so sustained rapid polling easily trips the limiter.","triggerScenarios":"Looping over many tokens without delay: for (const t of tokens) await formula(t); firing parallel requests with Promise.all over dozens of packages; a CI job re-running frequently against the same endpoints.","commonSituations":"Bulk scripts enumerating hundreds of formulae; monitoring/CI pipelines with short intervals; shared egress IPs (office/CI) where the combined request rate triggers 429 for everyone.","solutions":["Wait a few seconds and retry the same request (the throttle is short-lived).","Add delay/backoff between requests when iterating (e.g. sleep 1s per token, or exponential backoff on 429).","Batch or cache results locally to cut request volume, and prefer sequential requests over Promise.all bursts.","If you routinely need bulk data, download the API's bulk JSON files instead of per-token requests."],"exampleFix":"// before\nconst results = await Promise.all(tokens.map(t => formula(t))); // 429 burst\n// after\nconst results = [];\nfor (const t of tokens) {\n  results.push(await withRetry(() => formula(t), { on429: waitMs => sleep(waitMs) }));\n  await sleep(1000);\n}","handlingStrategy":"retry","validationCode":"const MIN_INTERVAL_MS = 1000;\nlet lastCall = 0;\nasync function throttledBrewFetch(url, label) {\n  const wait = lastCall + MIN_INTERVAL_MS - Date.now();\n  if (wait > 0) await sleep(wait);\n  lastCall = Date.now();\n  return brewFetch(url, label);\n}","typeGuard":"null","tryCatchPattern":"async function fetchWith429Retry(url, label, retries = 3) {\n  for (let i = 0; ; i++) {\n    try { return await brewFetch(url, label); }\n    catch (err) {\n      if (err instanceof CommandExecutionError && err.message.includes('429') && i < retries) {\n        await sleep(3000 * 2 ** i); // back off and retry\n        continue;\n      }\n      throw err;\n    }\n  }\n}","preventionTips":["Serialize requests with a >=1s gap instead of Promise.all bursts.","Cache responses locally; the API regenerates only daily so caching is safe.","Use Homebrew's bulk JSON endpoints for enumerations instead of per-token calls.","In CI, add jitter and backoff so shared egress IPs don't compound throttling."],"tags":["rate-limit","http-429","retry"],"backgroundTag":"rate-limit-exceeded","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}