jackwener/OpenCLI · warning · CommandExecutionError
${label} returned HTTP 429 (rate limited)
Error message
${label} returned HTTP 429 (rate limited) What it means
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.
Source
Thrown at clis/homebrew/utils.js:75
return s;
}
export async function brewFetch(url, label) {
let resp;
try {
resp = await fetch(url, { headers: { 'user-agent': UA, accept: 'application/json' } });
}
catch (err) {
throw new CommandExecutionError(
`${label} request failed: ${err?.message ?? err}`,
'Check that formulae.brew.sh is reachable from this network.',
);
}
if (resp.status === 404) {
throw new EmptyResultError(label, `Homebrew API returned 404 for ${url}.`);
}
if (resp.status === 429) {
throw new CommandExecutionError(
`${label} returned HTTP 429 (rate limited)`,
'Homebrew throttles bursts; wait a few seconds and retry.',
);
}
if (!resp.ok) {
throw new CommandExecutionError(`${label} returned HTTP ${resp.status}`);
}
let body;
try {
body = await resp.json();
}
catch (err) {
throw new CommandExecutionError(`${label} returned malformed JSON: ${err?.message ?? err}`);
}
return body;
}
/** Coerce a count value (which Homebrew analytics serves as `"139,972"`) to a plain number. */View on GitHub (pinned to 49907e53dc)
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.
Example fix
// before
const results = await Promise.all(tokens.map(t => formula(t))); // 429 burst
// after
const results = [];
for (const t of tokens) {
results.push(await withRetry(() => formula(t), { on429: waitMs => sleep(waitMs) }));
await sleep(1000);
} Defensive patterns
Strategy: retry
Validate before calling
const MIN_INTERVAL_MS = 1000;
let lastCall = 0;
async function throttledBrewFetch(url, label) {
const wait = lastCall + MIN_INTERVAL_MS - Date.now();
if (wait > 0) await sleep(wait);
lastCall = Date.now();
return brewFetch(url, label);
} Type guard
null
Try / catch
async function fetchWith429Retry(url, label, retries = 3) {
for (let i = 0; ; i++) {
try { return await brewFetch(url, label); }
catch (err) {
if (err instanceof CommandExecutionError && err.message.includes('429') && i < retries) {
await sleep(3000 * 2 ** i); // back off and retry
continue;
}
throw err;
}
}
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
- HTTP status errors: handling 4xx and 5xx responses — how to handle 4xx and 5xx responses properly.
Related errors
- coingecko derivatives returned HTTP 429 (rate limited)
- coingecko returned HTTP 429 (rate limited)
- ${label} returned HTTP 429 (rate limited)
- hf spaces returned HTTP 429 (rate limited)
- ${label} returned HTTP 429 (rate limited)
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/a3ca7517364722e9.
Report an issue: GitHub.