jackwener/OpenCLI · warning · CommandExecutionError
${label} returned HTTP 429 (rate limited)
Error message
${label} returned HTTP 429 (rate limited) What it means
cratesFetch throws this CommandExecutionError when crates.io answers HTTP 429, meaning the request was rate limited. crates.io deliberately throttles unauthenticated clients, so the library surfaces 429 as its own case with a 'wait and retry' hint instead of the generic non-ok branch.
Source
Thrown at clis/crates/utils.js:56
}
export async function cratesFetch(url, label) {
let resp;
try {
// crates.io requires a descriptive User-Agent per https://crates.io/data-access
resp = await fetch(url, { headers: { 'user-agent': UA, accept: 'application/json' } });
}
catch (err) {
throw new CommandExecutionError(
`${label} request failed: ${err?.message ?? err}`,
'Check that crates.io is reachable from this network.',
);
}
if (resp.status === 404) {
throw new EmptyResultError(label, `crates.io returned 404 for ${url}.`);
}
if (resp.status === 429) {
throw new CommandExecutionError(
`${label} returned HTTP 429 (rate limited)`,
'crates.io rate-limits unauthenticated traffic; 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;
}
View on GitHub (pinned to 49907e53dc)
Solutions
- Wait a few seconds and retry the command (crates.io suggests ≥1 req/sec, ideally 1 req/10s for bulk).
- Add throttling/backoff in your script: sleep between requests and retry on 429 with exponential backoff.
- Batch your lookups: query only the crates you need instead of enumerating.
- If behind a shared proxy/CI runner, retry later or run from a different network.
Example fix
// before
for (const name of names) { await fetchCrate(name); } // bursts -> 429
// after
for (const name of names) {
await fetchCrate(name);
await new Promise(r => setTimeout(r, 2000));
} Defensive patterns
Strategy: retry
Try / catch
async function fetchWithBackoff(fn, retries = 4) {
for (let i = 0; i < retries; i++) {
try { return await fn(); }
catch (err) {
if (!/429/.test(err.message) || i === retries - 1) throw err;
await new Promise(r => setTimeout(r, 2000 * 2 ** i));
}
}
} Prevention
- Keep crates.io traffic to <= ~1 request/second (ideally slower for bulk).
- Add sleeps between iterations in batch scripts and CI jobs.
- Avoid running parallel crates.io queries from one IP.
- Cache results you've already fetched instead of re-querying.
When it happens
Trigger: Calling cratesFetch (via any crates subcommand) more than ~1 request/second from an unauthenticated client, or running many queries back-to-back in a script from one IP.
Common situations: Batch scripts iterating dozens of crates; CI jobs making parallel crates.io calls; shared NAT/proxy IP already throttled; polling loops with no delay.
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)
- hf spaces returned HTTP 429 (rate limited)
- ${label} 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/b8b8646f65054f8d.
Report an issue: GitHub.