{"record":{"id":"3747130c96793536","repo":"jackwener/OpenCLI","slug":"twitter-bookmarks-archive-incomplete","errorCode":"twitter_bookmarks_archive_incomplete","errorMessage":"twitter_bookmarks_archive_incomplete: stopped after ${pages} page(s); completion cannot be proven","messagePattern":"twitter_bookmarks_archive_incomplete: stopped after (.+?) page\\(s\\); completion cannot be proven","errorType":"error_code","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/twitter/bookmarks.js","lineNumber":305,"sourceCode":"            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,\n                pages,\n                ...(exhausted ? {} : { cursor, resumeFile: resumeFile || null }),\n            };\n        }\n        if (fetchAll && !exhausted) {\n            throw new CommandExecutionError(\n                `twitter_bookmarks_archive_incomplete: stopped after ${pages} page(s); completion cannot be proven`,\n                resumeFile ? `Resume with --resume-file ${resumeFile}` : 'Rerun with --resume-file to preserve continuation state.',\n            );\n        }\n        const trimmed = fetchAll ? allTweets : allTweets.slice(0, limit);\n        return applyTopByEngagement(trimmed, topByEngagement);\n    },\n});\nexport const __test__ = {\n    parseBookmarks,\n    extractBookmarkTweet,\n    readResumeFile,\n};\n","sourceCodeStart":287,"sourceCodeEnd":319,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/twitter/bookmarks.js#L287-L319","documentation":"Thrown when a full-bookmark archive run (--all) stops paginating without reaching the natural end of the timeline, so the tool cannot prove every bookmark was captured. The library refuses to report an 'archive' result it cannot guarantee complete, and instead surfaces the resume cursor file so the run can be continued.","triggerScenarios":"fetchAll is true, the X bookmarks endpoint stops returning a cursor / exhausts pages before exhausted=true (rate limiting, API truncation, page cap), and a CommandExecutionError is raised with the page count.","commonSituations":"Large bookmark collections hitting API page limits or rate limits mid-run; X GraphQL changes that end pagination early; interrupted sessions where the cursor was lost before the resume file was set.","solutions":["Rerun with --resume-file <path> so pagination state (cursor) is persisted and can continue where the run stopped","Check for rate-limit headers/errors from X and wait before retrying the resume run","Verify the account's bookmark count against the pages fetched to see if truncation is systemic","Reduce per-request pressure (slower page delay, smaller page size) and resume again"],"exampleFix":"// before\nnpx opencli twitter bookmarks --all\n// after\nnpx opencli twitter bookmarks --all --resume-file ./bookmarks-resume.json\n# on stop:\nnpx opencli twitter bookmarks --all --resume-file ./bookmarks-resume.json --resume","handlingStrategy":"try-catch","validationCode":"const args = ['twitter','bookmarks','--all'];\nif (!args.includes('--resume-file')) {\n  const resume = `./bookmarks-resume-${Date.now()}.json`;\n  args.push('--resume-file', resume); // preserve cursor so stops are resumable\n}","typeGuard":"function isCompleteArchive(result) {\n  return result != null && result.complete === true && typeof result.pages === 'number';\n}","tryCatchPattern":"try {\n  const archive = await runTwitterBookmarks({ all: true, resumeFile });\n  if (!archive.complete) throw new Error('incomplete');\n} catch (err) {\n  if (String(err.message).startsWith('twitter_bookmarks_archive_incomplete')) {\n    // rerun with --resume-file <resumeFile> --resume\n  } else throw err;\n}","preventionTips":["Always pass --resume-file on --all runs so any stop is resumable","Run archives with generous rate-limit headroom and page delays","After completion, verify result.complete === true before trusting the dataset","Schedule large archives during low-traffic windows to reduce mid-run rate limits"],"tags":["twitter","pagination","archive","resume"],"backgroundTag":"pagination-incomplete","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}