jackwener/OpenCLI · warning · CommandExecutionError
${label} returned HTTP 429 (rate limited)
Error message
${label} returned HTTP 429 (rate limited) What it means
api.osv.dev responded with HTTP 429, meaning this client has exceeded OSV.dev's rate limit. The library throws a CommandExecutionError immediately instead of retrying, so the caller must back off and retry.
Source
Thrown at clis/osv/utils.js:112
return body;
}
export async function osvGet(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 api.osv.dev is reachable from this network.',
);
}
if (resp.status === 404) {
throw new EmptyResultError(label, `OSV.dev returned 404 for ${url}.`);
}
if (resp.status === 429) {
throw new CommandExecutionError(`${label} returned HTTP 429 (rate limited)`);
}
if (!resp.ok) {
throw new CommandExecutionError(`${label} returned HTTP ${resp.status}`);
}
return readJson(resp, label);
}
export async function osvPost(url, payload, label) {
let resp;
try {
resp = await fetch(url, {
method: 'POST',
headers: { 'user-agent': UA, accept: 'application/json', 'content-type': 'application/json' },
body: JSON.stringify(payload),
});
}
catch (err) {
throw new CommandExecutionError(View on GitHub (pinned to 49907e53dc)
Solutions
- Wait and retry after a delay (OSV.dev rate limits reset within minutes); implement exponential backoff
- Add caching so repeated vulnerability IDs are not re-fetched within a run
- Serialize or throttle requests (e.g. a few hundred ms delay between lookups) instead of firing them in parallel
- If usage is heavy, request an API key / higher quota from OSV.dev
Example fix
// before
const vuln = await osvGet(url, label); // throws on 429
// after
for (let i = 0; i < 3; i++) {
try { return await osvGet(url, label); }
catch (e) { if (!/429/.test(e.message)) throw e; await sleep(2 ** i * 1000); }
} Defensive patterns
Strategy: retry
Validate before calling
const sleep = ms => new Promise(r => setTimeout(r, ms)); // use with backoff between OSV calls
Type guard
function isRateLimited(err) { return err instanceof Error && /HTTP 429/.test(err.message); } Try / catch
for (let attempt = 0; attempt < 4; attempt++) {
try { return await vuln(id); }
catch (e) {
if (!isRateLimited(e) || attempt === 3) throw e;
await sleep(1000 * 2 ** attempt);
}
} Prevention
- Throttle OSV lookups (e.g. 200-500ms delay) when scanning many packages
- Cache vulnerability responses keyed by ID for the session duration
- Avoid parallel fan-out against api.osv.dev from CI
- Consider requesting elevated quota from OSV.dev for heavy usage
When it happens
Trigger: osvGet hit `${OSV_BASE}/v1/...` (via `vuln`) and the response status was 429 — triggered by issuing many vulnerability lookups in a short window from one IP without an API key.
Common situations: Batch-scanning many dependencies' advisories in a loop; CI pipelines querying OSV for every dependency concurrently; shared CI runner IPs already rate-limited by other jobs.
Understand the failure class
- HTTP status errors: handling 4xx and 5xx responses — how to handle 4xx and 5xx responses properly.
Related errors
- coingecko returned HTTP 429 (rate limited)
- coingecko returned HTTP 429 (rate limited)
- coingecko derivatives returned HTTP 429 (rate limited)
- coingecko returned HTTP 429 (rate limited)
- coingecko returned HTTP 429 (rate limited)
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/73b9dbfbd6f679d9.
Report an issue: GitHub.