{"record":{"id":"574f4e7c20a719de","repo":"jackwener/OpenCLI","slug":"twitter-likes-repeated-cursor","errorCode":"twitter_likes_repeated_cursor","errorMessage":"twitter_likes_repeated_cursor: archive completion cannot be proven; resume state was retained","messagePattern":"twitter_likes_repeated_cursor: archive completion cannot be proven; resume state was retained","errorType":"error_code","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/twitter/likes.js","lineNumber":344,"sourceCode":"                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: 'likes',\n                username,\n                outputFile: useOutputFile ? outputFile : null,\n            });\n            if (pageComplete) {\n                exhausted = true;\n                break;\n            }\n            if (nextCursor === cursor) {\n                throw new CommandExecutionError('twitter_likes_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            if (looksLikePrivateTwitterTimeline(lastRawResponse)) {\n                throw new EmptyResultError('twitter likes', `No likes returned for @${username} (Likes are private by default on X; only the account owner can view their own likes)`);\n            }\n            throw new EmptyResultError('twitter likes', `No likes found for @${username}`);\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,","sourceCodeStart":326,"sourceCodeEnd":362,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/twitter/likes.js#L326-L362","documentation":"CommandExecutionError (code twitter_likes_repeated_cursor) thrown during pagination when the API returns the same bottom cursor twice in a row without the page being provably complete. Because completion cannot be proven, the library aborts and deliberately retains the resume file so a later run can retry from the last good cursor instead of marking the archive done.","triggerScenarios":"In the pagination loop at clis/twitter/likes.js:344, pageComplete is false and nextCursor === cursor (the same cursor value returned by two consecutive Likes pages).","commonSituations":"Twitter serving stale/cached timeline pages; flaky API responses mid-pagination; Twitter backend bug returning the cursor but no new entries; intermittent auth degradation during a long --all fetch.","solutions":["Re-run the command with --resume: it continues from the retained cursor, often succeeding on retry.","Wait a few minutes (transient Twitter flakiness) then resume.","Re-authenticate if the session degraded mid-run, then resume.","If it persists on every attempt, treat the archive as complete-at-last-stable-page or update query IDs and retry."],"exampleFix":"// before\n$ cli twitter likes @user --all  # aborted: repeated cursor\n// after\n$ cli twitter likes @user --all --resume  # retry from retained cursor","handlingStrategy":"retry","validationCode":"// track cursors yourself and abort early on repetition\nlet lastCursor = null;\nif (nextCursor && nextCursor === lastCursor) {\n  await sleep(5000); // transient flakiness guard before resuming\n}","typeGuard":"null","tryCatchPattern":"try {\n  await cli.twitter.likes({ username, all: true, resume: true });\n} catch (e) {\n  if (e.code === 'twitter_likes_repeated_cursor') {\n    await sleep(backoff); // resume state was retained; retry continues from cursor\n    return retryWithResume();\n  }\n  throw e;\n}","preventionTips":["Always keep the resume file (the library retains it on purpose)","Retry with backoff; repeated cursors are often transient","Re-authenticate if long runs degrade mid-pagination","Cap attempts (e.g. 3 retries) before accepting a partial archive"],"tags":["pagination","resume","api"],"backgroundTag":"pagination-stuck","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}