{"record":{"id":"6846c18147cc1e49","repo":"jackwener/OpenCLI","slug":"label-returned-http-429-rate-limited-6846c1","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/rfc/utils.js","lineNumber":49,"sourceCode":"    return n;\n}\n\nexport async function rfcFetch(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 datatracker.ietf.org is reachable from this network.',\n        );\n    }\n    if (resp.status === 404) {\n        throw new EmptyResultError(label, `IETF datatracker 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    let body;\n    try {\n        body = await resp.json();\n    }\n    catch (err) {\n        throw new CommandExecutionError(`${label} returned malformed JSON: ${err?.message ?? err}`);\n    }\n    return body;\n}\n\n// IETF datatracker timestamps are like \"2022-02-19 08:46:51\" (no T, no Z)\n// or ISO with offset like \"2016-07-08T21:03:52+00:00\". Normalise to YYYY-MM-DD.\nexport function trimDate(value) {\n    const s = String(value ?? '').trim();","sourceCodeStart":31,"sourceCodeEnd":67,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/rfc/utils.js#L31-L67","documentation":"rfcFetch detects HTTP 429 from the IETF datatracker and throws CommandExecutionError noting the client is rate limited. The datatracker throttles clients making too many requests in a short window.","triggerScenarios":"Issuing many rapid rfc lookups (bulk scripts iterating hundreds of RFC numbers) until datatracker returns 429.","commonSituations":"Batch-fetching hundreds of RFCs in a loop, CI jobs hammering the API without delay, or shared-IP rate limits (office/CI egress IP throttled).","solutions":["Add a delay/backoff between requests (e.g. sleep 1s per fetch).","Respect Retry-After semantics; retry the failed request after waiting.","Cache previously fetched RFC metadata to avoid repeat calls.","Catch CommandExecutionError and retry with exponential backoff."],"exampleFix":"// before\nfor (const n of numbers) await fetchRfc(n);\n// after\nfor (const n of numbers) {\n  await fetchRfc(n);\n  await new Promise(r => setTimeout(r, 1000));\n}","handlingStrategy":"retry","validationCode":"null","typeGuard":"null","tryCatchPattern":"async function fetchWithBackoff(url, label, maxAttempts = 5) {\n  for (let i = 0; i < maxAttempts; i++) {\n    try {\n      return await rfcFetch(url, label);\n    } catch (err) {\n      const is429 = err instanceof CommandExecutionError && err.message.includes('429');\n      if (!is429 || i === maxAttempts - 1) throw err;\n      await new Promise(r => setTimeout(r, Math.min(2 ** i * 1000, 30000)));\n    }\n  }\n}","preventionTips":["Throttle bulk fetches (e.g. 1 request/second).","Honor Retry-After headers when present.","Cache responses to avoid repeated identical requests.","Run heavy batch jobs off shared egress IPs or during off-peak hours."],"tags":["rate-limit","http-429","throttling"],"backgroundTag":"http-429-rate-limited","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}