jackwener/OpenCLI · error · CommandExecutionError
twitter_bookmarks_repeated_cursor
twitter_bookmarks_repeated_cursor
Error message
twitter_bookmarks_repeated_cursor: archive completion cannot be proven; resume state was retained
What it means
Pagination is stuck: the API returned the same bottom cursor as the previous page instead of advancing or ending, so the CLI cannot prove the timeline is exhausted. clis/twitter/bookmarks.js:281 throws CommandExecutionError tagged twitter_bookmarks_repeated_cursor; the resume state is intentionally retained (not removed) so a later run can retry from the same point.
Source
Thrown at clis/twitter/bookmarks.js:281
else {
allTweets.push(...tweets);
}
const pageComplete = !nextCursor;
writeResumeFile(resumeFile, {
cursor: pageComplete ? null : nextCursor,
count: useOutputFile ? outputCount : allTweets.length,
tweets: useOutputFile ? undefined : allTweets,
updatedAt: new Date().toISOString(),
complete: pageComplete,
source: 'bookmarks',
outputFile: useOutputFile ? outputFile : null,
});
if (pageComplete) {
exhausted = true;
break;
}
if (nextCursor === cursor) {
throw new CommandExecutionError('twitter_bookmarks_repeated_cursor: archive completion cannot be proven; resume state was retained');
}
cursor = nextCursor;
}
const finalCount = useOutputFile ? outputCount : allTweets.length;
if (finalCount === 0) {
throw new EmptyResultError('twitter bookmarks', 'No bookmarks found for the logged-in account');
}
// Resume is only removed after the timeline is truly exhausted. Hitting
// --max-pages, partial API errors after some rows, or an interrupt must
// leave the resume file so the next run can continue.
if (exhausted)
removeResumeFile(resumeFile);
if (useOutputFile) {
return {
outputFile,
count: outputCount,
source: 'bookmarks',
complete: exhausted,View on GitHub (pinned to 49907e53dc)
Solutions
- Re-run the command later — the resume file was kept, so the run continues from the retained cursor.
- Refresh the query id (resolveTwitterQueryId) if the endpoint id is stale and causing degenerate pages.
- Check for a login/session refresh; soft auth failures can produce looping cursors.
- If it persists, start a fresh archive (remove resume + output files) and report the API anomaly.
Example fix
// before: assuming identical cursor means done
cursor = nextCursor; // loop continues forever or mis-ends
// after (library behavior): fail loudly and keep resume state
if (nextCursor === cursor) throw new CommandExecutionError('twitter_bookmarks_repeated_cursor: ...'); Defensive patterns
Strategy: retry
Validate before calling
// Track cursor progress in your wrapper so stalls are visible
let lastCursor = null;
for await (const page of bookmarkPages()) {
if (page.nextCursor === lastCursor) console.warn('Cursor stalled; aborting to retain resume state');
lastCursor = page.nextCursor;
} Type guard
function cursorAdvanced(prev, next) {
return typeof next === 'string' && next.length > 0 && next !== prev;
} Try / catch
try {
await runBookmarks(argv);
} catch (e) {
if (e instanceof CommandExecutionError && e.message.includes('twitter_bookmarks_repeated_cursor')) {
console.error('Pagination stalled; resume state retained — retry later or refresh the query id.');
process.exitCode = 1; // safe to re-run: resumes from saved cursor
} else throw e;
} Prevention
- Keep the resume file — the error is designed to be recoverable by re-running later.
- Refresh the query id if repeated-cursor failures cluster around a deployment.
- Avoid parallel runs against the same account (cache/cursor weirdness).
- Monitor for stalls and alert after N repeated-cursor failures.
When it happens
Trigger: Within the fetch loop, a page completes without the pageComplete guard triggering and parseBookmarks returns a nextCursor that equals the current cursor (nextCursor === cursor), meaning no forward progress.
Common situations: Twitter serving a cached/duplicated timeline page; cursor value looping due to API glitch or stale queryId; items filtered out by the seen-set causing cursor semantics mismatch; rate-limited responses returning degenerate payloads with the same cursor.
Related errors
- twitter_collection_repeated_cursor
- juejin recommend returned a malformed cursor
- juejin recommend returned has_more without a next cursor
- juejin cursor must be a non-negative decimal integer
- twitter_bookmarks_protocol_error
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/ecd22ce96988fc9e.
Report an issue: GitHub.