jackwener/OpenCLI · error · CommandExecutionError
WeRead book search result ${bookRank} is missing bookId
Error message
WeRead book search result ${bookRank} is missing bookId What it means
After selecting books[bookRank - 1], searchBookByQuery normalizes bookInfo and requires a non-empty bookId since every downstream call (reader URL, metadata, highlights) depends on it. When WeRead returns a result object lacking bookId, the CLI refuses to continue with a CommandExecutionError rather than producing a broken book record.
Source
Thrown at clis/weread/book-search.js:239
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`);
}
const htmlEntries = await loadSearchHtmlEntries(bookQuery);
selected.readerUrl = resolveReaderUrlForBook(selected, htmlEntries);
const readerMetadata = await loadReaderMetadata(selected.readerUrl);
return {
...selected,
...Object.fromEntries(Object.entries(readerMetadata ?? {}).filter(([, value]) => value != null && value !== '' && !(Array.isArray(value) && value.length === 0))),
};
}
async function resolveBookTarget(target, bookRank) {
if (/^https?:\/\//i.test(target)) {
const readerUrl = parseWereadReaderUrl(target);
if (!readerUrl) {
throw new ArgumentError('book URL must be a https://weread.qq.com/web/reader/<id> URL');
}
const metadata = await loadReaderMetadata(readerUrl);
if (!metadata?.bookId) {View on GitHub (pinned to 49907e53dc)
Solutions
- Pick a different book-rank whose result has a valid bookId.
- Verify the response schema against the current WeRead API and update the parser if the field was renamed.
- Fall back to resolving the book by URL or numeric bookId instead of search.
Defensive patterns
Strategy: try-catch
Type guard
function hasBookId(r) {
return r != null && typeof r === 'object' && typeof r.bookId === 'string' && r.bookId.length > 0;
} Try / catch
try {
return await resolveBookTarget(target, rank);
} catch (e) {
if (e instanceof CommandExecutionError && e.message.includes('missing bookId')) {
return resolveBookTarget(target, rank + 1); // try next search hit
}
throw e;
} Prevention
- Try a different rank when one result is malformed.
- Pin to numeric bookId for books you query repeatedly.
- Watch for WeRead API schema changes in CI tests.
When it happens
Trigger: WeRead's search endpoint returns a result whose bookInfo exists but has no/empty bookId field (API shape change, ad/promotional entries, or restricted books), and the caller's rank selects that entry.
Common situations: WeRead changes its search response schema; the selected hit is a special (non-book) card such as an ad or bundle; rate-limiting or anti-bot responses return degraded payloads.
Related errors
- WeRead in-book search returned malformed result
- WeRead in-book search returned malformed match
- book-rank must be <= ${books.length}
- WeRead in-book search returned malformed pagination state
- weread book-search: No matches for "${query}" in book ${book
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/a9ffd280acdd9255.
Report an issue: GitHub.