jackwener/OpenCLI · error · CommandExecutionError

Xiaoyuzhou history pagination repeated the same cursor

Error message

Xiaoyuzhou history pagination repeated the same cursor

What it means

During pagination, fetchHistory guards against a next-cursor that equals the special 'load more' sentinel or one already visited. Repeating a cursor would loop forever fetching the same page, so the library aborts with this error.

Source

Thrown at clis/xiaoyuzhou/history.js:215

                    durationSec: episode.durationSec,
                    progressSec: progress.progressSec,
                    progressPct: episode.durationSec !== null && progress.progressSec !== null
                        ? Number(((progress.progressSec / episode.durationSec) * 100).toFixed(1))
                        : null,
                    playedAt: progress.playedAt,
                    pubDate: episode.pubDate,
                    finished: episode.finished,
                    url: `https://www.xiaoyuzhoufm.com/episode/${episode.eid}`,
                });
            }
        }
        if (!fetchAll && rows.length >= limit) break;
        if (page.next === null) {
            exhausted = true;
            break;
        }
        if (page.next === loadMoreKey || seenCursors.has(page.next)) {
            throw new CommandExecutionError('Xiaoyuzhou history pagination repeated the same cursor');
        }
        if (pageNumber === maxPages) {
            throw new CommandExecutionError(
                `Xiaoyuzhou history stopped at the --max-pages safety limit (${maxPages}) before reaching the end`,
            );
        }
        seenCursors.add(page.next);
        loadMoreKey = page.next;
    }

    if (rows.length === 0) {
        throw new EmptyResultError('xiaoyuzhou history', 'The logged-in account has no playback history');
    }
    if (fetchAll && !exhausted) {
        throw new CommandExecutionError('Xiaoyuzhou history archive did not reach the end of pagination');
    }
    return rows;
}

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Log the raw page responses around the failure to see the duplicated cursor value and whether it is the sentinel.
  2. Check for an API change in cursor format and update cursor handling/encoding accordingly.
  3. Disable response caching/proxying so each page request hits the live API.
  4. Report the looping cursor to the API provider if the live service is at fault.

Example fix

// before
const next = page.next; // used as-is
// after (API changed cursor encoding)
const next = typeof page.next === 'string' && !page.next.startsWith('enc:')
  ? 'enc:' + btoa(page.next)
  : page.next;
Defensive patterns

Strategy: retry

Validate before calling

const seenCursors = new Set();
if (page.next && seenCursors.has(page.next)) throw new Error('cursor loop detected before continuing');
seenCursors.add(page.next);

Try / catch

try {
  const rows = await fetchHistory(args);
} catch (e) {
  if (e instanceof CommandExecutionError && /repeated the same cursor/.test(e.message)) {
    console.error('Upstream cursor bug or format change; disable caches and inspect cursors');
  } else throw e;
}

Prevention

When it happens

Trigger: The API returns the same next cursor twice in a row, returns a cursor equal to the current loadMoreKey, or a response shape change makes page.next stale/unchanged; also triggered when the loadMore sentinel value is echoed back as a real cursor.

Common situations: Upstream cursor-encoding changes (e.g. cursor now needs base64 wrapping); server bugs returning the last page's cursor again; proxies replaying cached responses; mock servers always returning a fixed next cursor.

Related errors


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