jackwener/OpenCLI · error · CommandExecutionError

WeRead in-book search returned malformed pagination state

Error message

WeRead in-book search returned malformed pagination state

What it means

When a full page is returned but data.hasMore cannot be interpreted as true/false/1/0/'1'/'0', parseHasMore returns null. Since the page was full (length === pageSize) the loop would otherwise continue blindly; the CLI throws CommandExecutionError because it cannot determine whether more pages exist.

Solutions

  1. Inspect the raw response to see how hasMore is now represented and update parseHasMore.
  2. Reduce the query scope/limit so fewer full pages are fetched.
  3. Retry if the response was a transient degraded envelope (auth/rate limit).

Example fix

// before
if (value === true || value === 1 || value === '1') return true;
// after (accept 'true'/'false' strings)
if (value === 'true' || value === true || value === 1 || value === '1') return true;
Defensive patterns

Strategy: try-catch

Type guard

function parseHasMoreSafe(v) {
  if (v === true || v === 1 || v === '1' || v === 'true') return true;
  if (v === false || v === 0 || v === '0' || v === 'false') return false;
  return null;
}

Try / catch

try {
  return await searchWithinBook(bookId, query);
} catch (e) {
  if (e instanceof CommandExecutionError && e.message.includes('malformed pagination state')) {
    return rowsSoFar; // accept what was collected
  }
  throw e;
}

Prevention

When it happens

Trigger: A full pageSize page arrives but data.hasMore is undefined, null, or an unexpected type (e.g. boolean-like string 'true', or the field renamed) — so pagination state is undecidable.

Common situations: WeRead API changing hasMore's representation (e.g. to 'true'/'false' strings or moving it into a nested object); error envelopes that keep result but drop hasMore.

Understand the failure class

Related errors


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

Appendix: source

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

                chapterIdx: parseOptionalFiniteNumber(item.chapterIdx),
                chapterUid: parseOptionalFiniteNumber(item.chapterUid),
                searchIdx,
            };
        });
        if (result.length === 0)
            break;
        rows.push(...result);
        const lastSearchIdx = result[result.length - 1].searchIdx;
        if (lastSearchIdx <= maxIdx)
            throw new CommandExecutionError('WeRead in-book search returned non-advancing searchIdx');
        maxIdx = lastSearchIdx;
        if (rows.length >= limit)
            break;
        const hasMore = parseHasMore(data?.hasMore);
        if (hasMore == null) {
            if (result.length < pageSize)
                break;
            throw new CommandExecutionError('WeRead in-book search returned malformed pagination state');
        }
        if (!hasMore)
            break;
    }
    if (rows.length === 0) {
        throw new EmptyResultError('weread book-search', `No matches for "${query}" in book ${bookId}`);
    }
    return rows.slice(0, limit);
}

function buildChapterMap(chapters) {
    const map = new Map();
    for (const chapter of chapters) {
        const chapterUid = parseOptionalFiniteNumber(chapter?.chapterUid);
        if (chapterUid == null)
            continue;
        map.set(chapterUid, {
            chapterIdx: parseOptionalFiniteNumber(chapter?.chapterIdx),

View on GitHub (pinned to 49907e53dc)