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

  1. Re-run the command later — the resume file was kept, so the run continues from the retained cursor.
  2. Refresh the query id (resolveTwitterQueryId) if the endpoint id is stale and causing degenerate pages.
  3. Check for a login/session refresh; soft auth failures can produce looping cursors.
  4. 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

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


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