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

  1. Pick a different book-rank whose result has a valid bookId.
  2. Verify the response schema against the current WeRead API and update the parser if the field was renamed.
  3. 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

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


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