{"record":{"id":"8bedef1af6686534","repo":"jackwener/OpenCLI","slug":"archive-search-failed-http-resp-status","errorCode":null,"errorMessage":"archive search failed: HTTP ${resp.status}","messagePattern":"archive search failed: HTTP (.+?)","errorType":"http","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/archive/search.js","lineNumber":76,"sourceCode":"        url.searchParams.set('rows', String(limit));\n        url.searchParams.set('sort[]', `${sort} desc`);\n        for (const fl of ['identifier', 'title', 'creator', 'date', 'mediatype', 'downloads']) {\n            url.searchParams.append('fl[]', fl);\n        }\n\n        let resp;\n        try {\n            resp = await fetch(url, {\n                headers: {\n                    'Accept': 'application/json',\n                    'User-Agent': 'opencli/1.0 (+https://github.com/jackwener/opencli)',\n                },\n            });\n        } catch (error) {\n            throw new CommandExecutionError(`archive search request failed: ${error?.message || error}`);\n        }\n        if (!resp.ok) {\n            throw new CommandExecutionError(`archive search failed: HTTP ${resp.status}`);\n        }\n        let data;\n        try {\n            data = await resp.json();\n        } catch (error) {\n            throw new CommandExecutionError(`archive search returned malformed JSON: ${error?.message || error}`);\n        }\n\n        const docs = data?.response?.docs;\n        if (!Array.isArray(docs)) {\n            throw new CommandExecutionError('archive search returned malformed payload: response.docs must be an array');\n        }\n        if (docs.length === 0) {\n            throw new EmptyResultError('archive search', `No items match \"${query}\" on archive.org.`);\n        }\n\n        return docs.slice(0, limit).map((d, i) => {\n            const id = String(d.identifier ?? '');","sourceCodeStart":58,"sourceCodeEnd":94,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/archive/search.js#L58-L94","documentation":"This CommandExecutionError is thrown when the archive.org advancedsearch endpoint returns an HTTP response whose status is not ok (resp.ok false, i.e. outside 2xx). The status code is embedded in the message. Unlike error 286, the request completed at the network level — the server answered with an error status.","triggerScenarios":"Calling `opencli archive search ...` and archive.org returns 403 (rate limiting/blocked UA/IP), 429 (too many requests), 500/503 (server-side incident), or 502/504 from its edge.","commonSituations":"Hammering the API in a loop and getting rate-limited (429); archive.org maintenance windows returning 503; datacenter IPs being blocked with 403; malformed query syntax causing server-side 400s.","solutions":["Read the HTTP status in the message: retry with backoff for 429/5xx, do not retry 4xx","Slow down or add jitter between successive searches to avoid rate limiting","Check https://status.archive.org for ongoing incidents on 5xx","Simplify or quote the query if a 400 suggests the server rejected the query syntax"],"exampleFix":"// before\nfor (const q of queries) rows.push(await run(['archive', 'search', q]));\n// after\nfor (const q of queries) {\n    try {\n        rows.push(await run(['archive', 'search', q]));\n    } catch (err) {\n        if (/HTTP (429|5\\d\\d)/.test(String(err?.message))) {\n            await new Promise(r => setTimeout(r, 2000));\n            rows.push(await run(['archive', 'search', q]));\n        } else { throw err; }\n    }\n}","handlingStrategy":"retry","validationCode":"// nothing to validate pre-call; status is server-side\n// throttle proactively:\nawait sleep(1000); // keep request rate under archive.org limits","typeGuard":"null","tryCatchPattern":"try {\n    rows = await run(['archive', 'search', query]);\n} catch (err) {\n    const m = /HTTP (\\d{3})/.exec(String(err?.message));\n    if (err instanceof CommandExecutionError && m && ['429','500','502','503','504'].includes(m[1])) {\n        await new Promise(r => setTimeout(r, 2000 * Number(m[1] === '429')));\n        rows = await run(['archive', 'search', query]);\n    } else { throw err; }\n}","preventionTips":["Never retry 4xx statuses other than 429","Add jittered backoff between searches to avoid rate limits","Monitor https://status.archive.org for 5xx-prone periods","Avoid looping searches at full speed in CI or scrapers"],"tags":["http","api","archive-org","cli"],"backgroundTag":"http-error-response","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}