jackwener/OpenCLI · error · CommandExecutionError

Could not parse a bookId from the reader URL

Error message

Could not parse a bookId from the reader URL

What it means

resolveBookTarget accepted the target as a well-formed reader URL, but loadReaderMetadata (which scrapes the page's window.__INITIAL_STATE__) returned metadata without a bookId. The CLI cannot identify the book, so it throws CommandExecutionError.

Source

Thrown at clis/weread/book-search.js:258

    }
    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) {
            throw new CommandExecutionError('Could not parse a bookId from the reader URL');
        }
        return metadata;
    }
    if (/^\d+$/.test(target)) {
        const metadata = {};
        metadata.bookId = target;
        metadata.title = '';
        metadata.author = '';
        metadata.readerUrl = '';
        metadata.chapters = [];
        return metadata;
    }
    return searchBookByQuery(target, bookRank);
}

async function searchWithinBook(bookId, query, limit, fragmentSize) {
    const rows = [];
    let maxIdx = 0;

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Retry with a valid logged-in session/cookie so the real book page loads.
  2. Verify the URL actually resolves to a live book (open it in a browser).
  3. If the page loads fine in a browser, inspect window.__INITIAL_STATE__ for schema changes and update the extractor.
  4. Pass the numeric bookId directly instead of the URL.
Defensive patterns

Strategy: retry

Type guard

function metadataHasBookId(m) {
  return m != null && typeof m === 'object' && typeof m.bookId === 'string' && m.bookId.length > 0;
}

Try / catch

try {
  return await resolveBookTarget(url, rank);
} catch (e) {
  if (e instanceof CommandExecutionError && e.message.includes('Could not parse a bookId')) {
    await sleep(2000);
    return resolveBookTarget(url, rank); // transient page / rate limit
  }
  throw e;
}

Prevention

When it happens

Trigger: parseWereadReaderUrl succeeded but the fetched reader page's embedded initial state lacks bookId — e.g. WeRead returned a login/captcha/error page instead of the book page, or the page template changed.

Common situations: Not logged in or session expired so WeRead serves an interstitial; anti-bot / rate limiting serving degraded HTML; a WeRead frontend update renaming or removing the bookId field in __INITIAL_STATE__.

Related errors


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