jackwener/OpenCLI · error · CommandExecutionError

WeRead in-book search returned malformed match

Error message

WeRead in-book search returned malformed match

What it means

While mapping data.result, searchWithinBook requires each match to be a non-null object. This second variant of the 'malformed match' error fires when an entry of the result array is null or not an object, so abstract/searchIdx extraction would crash.

Source

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

    const rows = [];
    let maxIdx = 0;
    while (rows.length < limit) {
        const remaining = limit - rows.length;
        const pageSize = remaining < SEARCH_PAGE_SIZE ? remaining : SEARCH_PAGE_SIZE;
        const url = new URL('/web/book/search', WEREAD_WEB_ORIGIN);
        url.searchParams.set('bookId', bookId);
        url.searchParams.set('keyword', query);
        url.searchParams.set('maxIdx', String(maxIdx));
        url.searchParams.set('count', String(pageSize));
        url.searchParams.set('fragmentSize', String(fragmentSize));
        url.searchParams.set('onlyCount', '0');
        const data = await fetchJson(url, 'WeRead in-book search');
        if (!Array.isArray(data?.result)) {
            throw new CommandExecutionError('WeRead in-book search returned malformed result');
        }
        const result = data.result.map((item) => {
            if (!item || typeof item !== 'object') {
                throw new CommandExecutionError('WeRead in-book search returned malformed match');
            }
            const snippet = normalizeSearchText(item.abstract);
            const searchIdx = parseOptionalFiniteNumber(item.searchIdx);
            if (!snippet || searchIdx == null || searchIdx <= 0) {
                throw new CommandExecutionError('WeRead in-book search returned malformed match');
            }
            return {
                ...item,
                abstract: snippet,
                chapterIdx: parseOptionalFiniteNumber(item.chapterIdx),
                chapterUid: parseOptionalFiniteNumber(item.chapterUid),
                searchIdx,
            };
        });
        if (result.length === 0)
            break;
        rows.push(...result);
        const lastSearchIdx = result[result.length - 1].searchIdx;

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Retry the search; transient nulls often disappear on a fresh query.
  2. Narrow the query so problematic matches are excluded.
  3. If reproducible, filter out non-object items client-side or update the parser to skip them.

Example fix

// before
const result = data.result.map((item) => { ... });
// after
const result = data.result.filter((item) => item && typeof item === 'object').map((item) => { ... });
Defensive patterns

Strategy: type-guard

Type guard

function isMatchObject(item) {
  return item != null && typeof item === 'object' && !Array.isArray(item);
}

Try / catch

try {
  return await searchWithinBook(bookId, query);
} catch (e) {
  if (e instanceof CommandExecutionError && e.message.includes('malformed match')) {
    return []; // tolerate degraded results
  }
  throw e;
}

Prevention

When it happens

Trigger: The result array contains null, a string, or a number element — e.g. WeRead inserts placeholder/null entries into result for removed or censored matches.

Common situations: Books with recently edited/deleted chapters can yield null match slots; API schema drift; partial responses under rate limiting.

Understand the failure class

Related errors


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