jackwener/OpenCLI · error · CommandExecutionError
WeRead book search returned malformed books
Error message
WeRead book search returned malformed books
What it means
searchBookByQuery calls the /web/search/global JSON endpoint and expects data.books to be an array; when the shape differs it throws CommandExecutionError 'WeRead book search returned malformed books'. This is a response-shape contract check: the HTTP call succeeded but the payload does not match the expected schema.
Source
Thrown at clis/weread/book-search.js:221
const title = normalizeSearchText(book.title);
const author = normalizeSearchText(book.author);
if (!title)
return '';
if (author) {
const exact = htmlEntries.filter((entry) => normalizeSearchText(entry.title) === title && normalizeSearchText(entry.author) === author);
if (exact.length === 1)
return exact[0].readerUrl;
}
const sameTitle = htmlEntries.filter((entry) => normalizeSearchText(entry.title) === title);
return sameTitle.length === 1 ? sameTitle[0].readerUrl : '';
}
async function searchBookByQuery(bookQuery, bookRank) {
const url = new URL('/web/search/global', `${WEREAD_WEB_ORIGIN}/web`);
url.searchParams.set('keyword', bookQuery);
const data = await fetchJson(url, 'WeRead book search');
if (!Array.isArray(data?.books)) {
throw new CommandExecutionError('WeRead book search returned malformed books');
}
const books = data.books;
if (books.length === 0) {
throw new EmptyResultError('weread book-search', `No WeRead books found for "${bookQuery}"`);
}
if (bookRank > books.length) {
throw new ArgumentError(`book-rank must be <= ${books.length}`, `Only ${books.length} book search result(s) were returned for "${bookQuery}"`);
}
const bookInfo = books[bookRank - 1]?.bookInfo ?? {};
const selected = {
bookId: normalizeSearchText(bookInfo.bookId),
title: normalizeSearchText(bookInfo.title),
author: normalizeSearchText(bookInfo.author),
readerUrl: '',
chapters: [],
};
if (!selected.bookId) {
throw new CommandExecutionError(`WeRead book search result ${bookRank} is missing bookId`);View on GitHub (pinned to 49907e53dc)
Solutions
- Log the raw response body to see the actual shape WeRead returned
- Update the CLI parser if the API schema changed (books moved/renamed)
- Check whether the endpoint now requires authentication/cookies and supply them
- Verify no proxy is rewriting the response
- Report/patch the schema check in searchBookByQuery to match the new shape
Example fix
// before
const data = await fetchJson(url, 'WeRead book search');
if (!Array.isArray(data?.books)) {
throw new CommandExecutionError('WeRead book search returned malformed books');
}
// after (accept alternative shapes and surface the real body)
const data = await fetchJson(url, 'WeRead book search');
const books = data?.books ?? data?.results?.books;
if (!Array.isArray(books)) {
throw new CommandExecutionError('WeRead book search returned malformed books: ' + JSON.stringify(data).slice(0, 200));
} Defensive patterns
Strategy: type-guard
Validate before calling
// Validate the API payload before handing it to the CLI's expectations
const data = await fetchJson(url, 'WeRead book search');
if (!isBooksPayload(data)) {
console.error('Unexpected WeRead payload:', JSON.stringify(data).slice(0, 200));
} Type guard
function isBooksPayload(v) {
return v !== null && typeof v === 'object'
&& Array.isArray(v.books)
&& v.books.every((b) => b === null || typeof b === 'object');
} Try / catch
try {
await runCommand(['book-search', '--query', q]);
} catch (e) {
if (e instanceof CommandExecutionError && e.message.includes('malformed books')) {
console.error('WeRead API schema changed or auth is required. Dump the raw response and update the parser.');
} else throw e;
} Prevention
- Log the raw JSON body when this fires to detect schema drift quickly
- Version-pin or monitor the upstream API for changes
- Check whether a login/captcha JSON body ({code,msg}) replaced the expected payload
- Add CI smoke tests that exercise the real search endpoint shape
When it happens
Trigger: WeRead returns a valid 200 JSON body whose top-level structure changed (e.g. {results:...} instead of {books:[...]}), a login/captcha JSON object ({code:..., msg:...}), or null/undefined data from an unexpected payload.
Common situations: Silent API schema changes by WeRead, authenticated-only responses returned without an error status, regional API variants, or a reverse proxy substituting its own JSON error body.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- MiniMax music returned a malformed response envelope
- MiniMax music response is missing integer base_resp.status_c
- returned malformed items payload
- LinkedIn connection miniProfile field ${field} was malformed
- LinkedIn connections API returned a malformed payload: missi
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/47df8bf792203c66.
Report an issue: GitHub.