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

  1. Wait and retry after a delay (OSV.dev rate limits reset within minutes); implement exponential backoff
  2. Add caching so repeated vulnerability IDs are not re-fetched within a run
  3. Serialize or throttle requests (e.g. a few hundred ms delay between lookups) instead of firing them in parallel
  4. 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

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

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/73b9dbfbd6f679d9. Report an issue: GitHub.