jackwener/OpenCLI · error · CommandExecutionError
WeRead search API returned an unreadable books payload
Error message
WeRead search API returned an unreadable books payload
What it means
Thrown in the `weread search` command when the public /search/global API response is fetched but its `books` field is not an array. The library uses `data?.books ?? []` then explicitly checks Array.isArray, so a payload where `books` is an object, string, or structurally unexpected shape surfaces this CommandExecutionError instead of crashing later on .map().
Source
Thrown at clis/weread/search.js:152
name: 'search',
access: 'read',
description: 'Search books on WeRead',
domain: 'weread.qq.com',
strategy: Strategy.PUBLIC,
browser: false,
args: [
{ name: 'query', positional: true, required: true, help: 'Search keyword' },
{ name: 'limit', type: 'int', default: 10, help: 'Max results' },
],
columns: ['rank', 'title', 'author', 'bookId', 'url'],
func: async (args) => {
const [data, htmlEntries] = await Promise.all([
fetchWebApi('/search/global', { keyword: args.query }),
loadSearchHtmlEntries(String(args.query ?? '')),
]);
const books = data?.books ?? [];
if (!Array.isArray(books)) {
throw new CommandExecutionError('WeRead search API returned an unreadable books payload');
}
if (books.length === 0) {
throw new EmptyResultError('weread search', `No books were returned for query ${args.query}.`);
}
const { exactQueues, titleOnlyQueues } = buildSearchUrlQueues(htmlEntries);
const apiIdentityCounts = countSearchIdentities(books.map((item) => ({
title: item.bookInfo?.title ?? '',
author: item.bookInfo?.author ?? '',
})));
const htmlIdentityCounts = countSearchIdentities(htmlEntries.filter((entry) => entry.author));
const apiTitleCounts = countSearchTitles(books.map((item) => ({ title: item.bookInfo?.title ?? '' })));
const htmlTitleCounts = countSearchTitles(htmlEntries);
return books.slice(0, Number(args.limit)).map((item, i) => {
const title = item.bookInfo?.title ?? '';
const author = item.bookInfo?.author ?? '';
return {
rank: i + 1,
title,View on GitHub (pinned to 49907e53dc)
Solutions
- Log the raw response (resp.json() output) from /search/global to see the actual shape WeRead now returns.
- Check whether the response is an API error envelope (errCode/errmsg) and handle/surface that before indexing .books.
- If WeRead renamed the field, update the extraction in search.js (e.g. data.books → data.result.books or similar) and pin/patch your CLI version.
- File/track an upstream issue if it's a schema change; meanwhile rely on the HTML entries path or another search method.
- Add a defensive parse helper that validates the payload shape before use so future changes produce clearer diagnostics.
Example fix
// before
const books = data?.books ?? [];
if (!Array.isArray(books)) throw new CommandExecutionError('...unreadable books payload');
// after
const raw = data?.books ?? data?.result?.books ?? data?.data?.books;
const books = Array.isArray(raw) ? raw : (() => { console.error('unexpected payload:', JSON.stringify(data).slice(0, 500)); throw new CommandExecutionError('...unreadable books payload'); })(); Defensive patterns
Strategy: type-guard
Type guard
function hasBooksArray(data) {
return data != null && typeof data === 'object' && Array.isArray(data.books);
}
// usage: if (!hasBooksArray(data)) { /* handle schema drift before calling the command */ } Try / catch
try {
const rows = await runWereadSearch(query);
} catch (e) {
if (String(e.message).includes('unreadable books payload')) {
console.error('WeRead /search/global schema changed; inspect raw response and update parsing');
return fallbackToHtmlOnlyResults();
}
throw e;
} Prevention
- Validate API payloads with a schema/type guard at the boundary before processing
- Log raw /search/global responses periodically to detect schema drift early
- Pin and test against a known-good CLI version; review WeRead API changelogs
- Handle API error envelopes (errCode/errmsg) before reading data.books
When it happens
Trigger: fetchWebApi('/search/global', { keyword }) returns JSON but `data.books` is not an array — e.g. WeRead changed the response schema, returned {books: {...}} or an error envelope like {errCode, errMsg} with no books field, or returned an HTML/edge page that accidentally parsed as JSON.
Common situations: WeRead shipping an API contract change (relocating results to another field); a captive portal / anti-bot JSON error response; the keyword triggering a different error envelope from the API.
Related errors
- Zhihu answer detail returned a malformed payload
- 12306 ${endpoint} returned an unexpected payload shape
- ${label} returned an unexpected payload shape; expected an o
- archive snapshots returned malformed CDX payload: top-level
- archive snapshots returned malformed CDX payload: header row
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/5c2e0283a369ab27.
Report an issue: GitHub.