{"record":{"id":"76193cdadc230d8a","repo":"jackwener/OpenCLI","slug":"label-returned-http-429-rate-limited-76193c","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/flathub/utils.js","lineNumber":66,"sourceCode":"    let resp;\n    try {\n        resp = await fetch(url, {\n            method: init?.method ?? 'GET',\n            headers: { 'user-agent': UA, accept: 'application/json', ...(init?.headers ?? {}) },\n            body: init?.body,\n        });\n    }\n    catch (err) {\n        throw new CommandExecutionError(\n            `${label} request failed: ${err?.message ?? err}`,\n            'Check that flathub.org is reachable from this network.',\n        );\n    }\n    if (resp.status === 404) {\n        throw new EmptyResultError(label, `Flathub 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\nexport function joinList(value, max = 10) {\n    if (!Array.isArray(value)) return '';\n    const items = value.filter((v) => typeof v === 'string' && v.trim());\n    if (items.length === 0) return '';","sourceCodeStart":48,"sourceCodeEnd":84,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/flathub/utils.js#L48-L84","documentation":"flathubFetch throws CommandExecutionError '<label> returned HTTP 429 (rate limited)' when flathub.org responds with 429, meaning the client exceeded Flathub's request rate limits. The library raises it as a distinct error so callers can recognize throttling and back off rather than hammering the API.","triggerScenarios":"Looping over many appIds calling the appstream endpoint in rapid succession; concurrent parallel requests from scripts/CI; retry loops without backoff after earlier failures; shared IP (CI runner, corporate NAT) already rate-limited.","commonSituations":"Batch scripts fetching metadata for hundreds of apps; automated CI jobs polling Flathub; multiple teammates behind one NAT IP; aggressive retry logic after network errors.","solutions":["Add a delay between requests (e.g. 1-2s sleep) or implement exponential backoff on 429","Batch or deduplicate requests — cache appstream results locally instead of refetching","Reduce concurrency: run requests sequentially or with a small parallelism cap","Wait and retry later if a shared IP is being throttled"],"exampleFix":"// before\nfor (const id of ids) await appInfo(id); // hammers the API\n// after\nfor (const id of ids) {\n  await appInfo(id);\n  await new Promise((r) => setTimeout(r, 1000));\n}","handlingStrategy":"retry","validationCode":"// Throttle before calling: at most 1 request per second\nconst sleep = (ms) => new Promise((r) => setTimeout(r, ms));\nawait sleep(1000); // between flathub calls in a loop","typeGuard":"null","tryCatchPattern":"async function withBackoff(fn, attempts = 4) {\n  for (let i = 0; i < attempts; i++) {\n    try { return await fn(); }\n    catch (err) {\n      if (!/HTTP 429/.test(err.message) || i === attempts - 1) throw err;\n      await new Promise((r) => setTimeout(r, 2 ** i * 1000));\n    }\n  }\n}","preventionTips":["Serialize flathub requests with a delay instead of firing them in parallel","Cache appstream responses locally to avoid refetching the same app","Avoid tight retry loops without backoff — they worsen rate limiting","Account for shared IPs (CI runners, office NAT) that may already be throttled"],"tags":["rate-limit","http-429","http-api"],"backgroundTag":"http-429-rate-limited","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}