{"record":{"id":"582a96356beb4f57","repo":"jackwener/OpenCLI","slug":"archive-search-returned-malformed-payload-respons","errorCode":null,"errorMessage":"archive search returned malformed payload: response.docs must be an array","messagePattern":"archive search returned malformed payload: response\\.docs must be an array","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/archive/search.js","lineNumber":87,"sourceCode":"                    '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 ?? '');\n            if (!IDENTIFIER_RE.test(id)) {\n                throw new CommandExecutionError('archive search returned malformed payload: result row is missing a stable identifier');\n            }\n            const downloads = Number(d.downloads ?? 0);\n            if (!Number.isFinite(downloads)) {\n                throw new CommandExecutionError(`archive search returned malformed payload for \"${id}\": downloads must be numeric`);\n            }\n            const creator = Array.isArray(d.creator) ? d.creator.join(', ') : String(d.creator ?? '');\n            return {\n                rank: i + 1,\n                identifier: id,","sourceCodeStart":69,"sourceCodeEnd":105,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/archive/search.js#L69-L105","documentation":"This CommandExecutionError is thrown when the archive.org advancedsearch response parses as JSON but `data.response.docs` is missing or not an array. The CLI expects the standard Lucene/advancedsearch envelope ({ response: { docs: [...] } }) and refuses to proceed on any other shape. It indicates an unexpected API payload rather than caller input error.","triggerScenarios":"Calling `opencli archive search ...` when archive.org returns a JSON error object (e.g. { error: ... }) instead of the response envelope, an API schema change, or an intermediary returning valid JSON of the wrong shape (e.g. a JSON error page from a proxy).","commonSituations":"Malformed query syntax that the backend rejects but returns as JSON 200; archive.org API drift or beta endpoints; corporate proxies substituting their own JSON error payloads; script errors hitting a mirror/clone with a different response shape.","solutions":["Inspect the raw JSON (`curl ... | jq`) to see what shape was actually returned","Check the query syntax — backend-rejected queries may come back as JSON without a response.docs envelope","Retry later in case of archive.org schema changes or incidents; check status.archive.org","Catch this error and surface the raw payload for diagnostics instead of letting it fail silently in pipelines"],"exampleFix":"// before\nconst { docs } = await searchArchive(query);\n// after\nconst result = await searchArchive(query);\nif (!Array.isArray(result?.response?.docs)) {\n    console.error('unexpected payload:', JSON.stringify(result).slice(0, 500));\n    process.exit(1);\n}\nconst { docs } = result.response;","handlingStrategy":"type-guard","validationCode":"const raw = await fetch(url).then(r => r.json());\nif (!Array.isArray(raw?.response?.docs)) {\n    console.error('unexpected envelope:', JSON.stringify(raw).slice(0, 500));\n}","typeGuard":"function hasDocsEnvelope(data) {\n    return Boolean(data) && typeof data === 'object' && Array.isArray(data?.response?.docs);\n}","tryCatchPattern":"try {\n    rows = await run(['archive', 'search', query]);\n} catch (err) {\n    if (err instanceof CommandExecutionError && err.message.includes('response.docs must be an array')) {\n        console.error('archive.org returned a non-standard envelope; check query syntax or API status');\n    } else { throw err; }\n}","preventionTips":["Validate the { response: { docs } } envelope whenever you call archive.org directly","Check query syntax — backend-rejected queries can come back without the envelope","Pin to documented endpoints and watch for archive.org API changes","Log the raw JSON body on this failure for diagnostics"],"tags":["payload-validation","api","archive-org","cli"],"backgroundTag":"unexpected-api-response-shape","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}