jackwener/OpenCLI · error · CommandExecutionError

WeRead in-book search returned non-advancing searchIdx

Error message

WeRead in-book search returned non-advancing searchIdx

What it means

searchWithinBook paginates by searchIdx: each page's last match must have a searchIdx strictly greater than the previous maxIdx. When the API returns a page whose last searchIdx <= maxIdx, the loop would fetch the same page forever, so the CLI throws CommandExecutionError instead.

Solutions

  1. Lower the limit so fewer pages are requested before the cursor misbehaves.
  2. Retry the search — re-ranking between pages is often transient.
  3. Add a page-count cap in your own wrapper and stop before the error.
  4. Report the cursor bug if it reproduces consistently for one book/query.
Defensive patterns

Strategy: try-catch

Try / catch

try {
  return await searchWithinBook(bookId, query, { limit });
} catch (e) {
  if (e instanceof CommandExecutionError && e.message.includes('non-advancing searchIdx')) {
    return searchWithinBook(bookId, query, { limit: Math.min(limit, 50) }); // smaller limit, fewer pages
  }
  throw e;
}

Prevention

When it happens

Trigger: The in-book search endpoint returns a page whose final match's searchIdx does not advance past maxIdx — duplicated or re-sorted results, or a server bug after many pages.

Common situations: Deep pagination into large books where WeRead's index misbehaves; results re-ranked between requests; concurrent edits to the book shifting indices.

Related errors


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

Appendix: source

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

            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;
        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);
}

View on GitHub (pinned to 49907e53dc)