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

  1. Log the raw response (resp.json() output) from /search/global to see the actual shape WeRead now returns.
  2. Check whether the response is an API error envelope (errCode/errmsg) and handle/surface that before indexing .books.
  3. 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.
  4. File/track an upstream issue if it's a schema change; meanwhile rely on the HTML entries path or another search method.
  5. 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

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


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/5c2e0283a369ab27. Report an issue: GitHub.