{"record":{"id":"73b9dbfbd6f679d9","repo":"jackwener/OpenCLI","slug":"label-returned-http-429-rate-limited-73b9db","errorCode":null,"errorMessage":"${label} returned HTTP 429 (rate limited)","messagePattern":"(.+?) returned HTTP 429 \\(rate limited\\)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":429,"severity":"warning","filePath":"clis/osv/utils.js","lineNumber":112,"sourceCode":"    return body;\n}\n\nexport async function osvGet(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 api.osv.dev is reachable from this network.',\n        );\n    }\n    if (resp.status === 404) {\n        throw new EmptyResultError(label, `OSV.dev returned 404 for ${url}.`);\n    }\n    if (resp.status === 429) {\n        throw new CommandExecutionError(`${label} returned HTTP 429 (rate limited)`);\n    }\n    if (!resp.ok) {\n        throw new CommandExecutionError(`${label} returned HTTP ${resp.status}`);\n    }\n    return readJson(resp, label);\n}\n\nexport async function osvPost(url, payload, label) {\n    let resp;\n    try {\n        resp = await fetch(url, {\n            method: 'POST',\n            headers: { 'user-agent': UA, accept: 'application/json', 'content-type': 'application/json' },\n            body: JSON.stringify(payload),\n        });\n    }\n    catch (err) {\n        throw new CommandExecutionError(","sourceCodeStart":94,"sourceCodeEnd":130,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/osv/utils.js#L94-L130","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nconst vuln = await osvGet(url, label); // throws on 429\n// after\nfor (let i = 0; i < 3; i++) {\n  try { return await osvGet(url, label); }\n  catch (e) { if (!/429/.test(e.message)) throw e; await sleep(2 ** i * 1000); }\n}","handlingStrategy":"retry","validationCode":"const sleep = ms => new Promise(r => setTimeout(r, ms)); // use with backoff between OSV calls","typeGuard":"function isRateLimited(err) { return err instanceof Error && /HTTP 429/.test(err.message); }","tryCatchPattern":"for (let attempt = 0; attempt < 4; attempt++) {\n  try { return await vuln(id); }\n  catch (e) {\n    if (!isRateLimited(e) || attempt === 3) throw e;\n    await sleep(1000 * 2 ** attempt);\n  }\n}","preventionTips":["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"],"tags":["rate-limit","http-429","osv"],"backgroundTag":"rate-limit-exceeded","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}