{"record":{"id":"ecd22ce96988fc9e","repo":"jackwener/OpenCLI","slug":"twitter-bookmarks-repeated-cursor","errorCode":"twitter_bookmarks_repeated_cursor","errorMessage":"twitter_bookmarks_repeated_cursor: archive completion cannot be proven; resume state was retained","messagePattern":"twitter_bookmarks_repeated_cursor: archive completion cannot be proven; resume state was retained","errorType":"error_code","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/twitter/bookmarks.js","lineNumber":281,"sourceCode":"            else {\n                allTweets.push(...tweets);\n            }\n            const pageComplete = !nextCursor;\n            writeResumeFile(resumeFile, {\n                cursor: pageComplete ? null : nextCursor,\n                count: useOutputFile ? outputCount : allTweets.length,\n                tweets: useOutputFile ? undefined : allTweets,\n                updatedAt: new Date().toISOString(),\n                complete: pageComplete,\n                source: 'bookmarks',\n                outputFile: useOutputFile ? outputFile : null,\n            });\n            if (pageComplete) {\n                exhausted = true;\n                break;\n            }\n            if (nextCursor === cursor) {\n                throw new CommandExecutionError('twitter_bookmarks_repeated_cursor: archive completion cannot be proven; resume state was retained');\n            }\n            cursor = nextCursor;\n        }\n        const finalCount = useOutputFile ? outputCount : allTweets.length;\n        if (finalCount === 0) {\n            throw new EmptyResultError('twitter bookmarks', 'No bookmarks found for the logged-in account');\n        }\n        // Resume is only removed after the timeline is truly exhausted. Hitting\n        // --max-pages, partial API errors after some rows, or an interrupt must\n        // leave the resume file so the next run can continue.\n        if (exhausted)\n            removeResumeFile(resumeFile);\n        if (useOutputFile) {\n            return {\n                outputFile,\n                count: outputCount,\n                source: 'bookmarks',\n                complete: exhausted,","sourceCodeStart":263,"sourceCodeEnd":299,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/twitter/bookmarks.js#L263-L299","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: assuming identical cursor means done\ncursor = nextCursor; // loop continues forever or mis-ends\n// after (library behavior): fail loudly and keep resume state\nif (nextCursor === cursor) throw new CommandExecutionError('twitter_bookmarks_repeated_cursor: ...');","handlingStrategy":"retry","validationCode":"// Track cursor progress in your wrapper so stalls are visible\nlet lastCursor = null;\nfor await (const page of bookmarkPages()) {\n  if (page.nextCursor === lastCursor) console.warn('Cursor stalled; aborting to retain resume state');\n  lastCursor = page.nextCursor;\n}","typeGuard":"function cursorAdvanced(prev, next) {\n  return typeof next === 'string' && next.length > 0 && next !== prev;\n}","tryCatchPattern":"try {\n  await runBookmarks(argv);\n} catch (e) {\n  if (e instanceof CommandExecutionError && e.message.includes('twitter_bookmarks_repeated_cursor')) {\n    console.error('Pagination stalled; resume state retained — retry later or refresh the query id.');\n    process.exitCode = 1; // safe to re-run: resumes from saved cursor\n  } else throw e;\n}","preventionTips":["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."],"tags":["pagination","twitter","cursor","protocol"],"backgroundTag":"pagination-cursor-stuck","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}