{"record":{"id":"67535bac922d70a8","repo":"jackwener/OpenCLI","slug":"twitter-bookmarks-output-file-is-missing-for-resum","errorCode":null,"errorMessage":"Twitter bookmarks output file is missing for resume state: ${outputFile}","messagePattern":"Twitter bookmarks output file is missing for resume state: (.+?)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/twitter/bookmarks.js","lineNumber":220,"sourceCode":"            throw new ArgumentError('--output-file requires --resume-file so partial archives remain resumable');\n        }\n        const cookies = await page.getCookies({ url: 'https://x.com' });\n        const ct0 = cookies.find((c) => c.name === 'ct0')?.value || null;\n        if (!ct0)\n            throw new AuthRequiredError('x.com', 'Not logged into x.com (no ct0 cookie)');\n        const queryId = await resolveTwitterQueryId(page, 'Bookmarks', BOOKMARKS_QUERY_ID);\n        const headers = JSON.stringify({\n            'Authorization': `Bearer ${decodeURIComponent(TWITTER_BEARER_TOKEN)}`,\n            'X-Csrf-Token': ct0,\n            'X-Twitter-Auth-Type': 'OAuth2Session',\n            'X-Twitter-Active-User': 'yes',\n        });\n        const resumed = fetchAll ? readResumeFile(resumeFile, {\n            source: 'bookmarks',\n            outputFile: useOutputFile ? outputFile : null,\n        }) : null;\n        if (useOutputFile && resumed && resumed.count > 0 && !fs.existsSync(outputFile)) {\n            throw new CommandExecutionError(`Twitter bookmarks output file is missing for resume state: ${outputFile}`);\n        }\n        if (useOutputFile && !resumed && fs.existsSync(outputFile)) {\n            throw new ArgumentError(`Refusing to overwrite existing Twitter bookmarks output file: ${outputFile}`);\n        }\n        const allTweets = useOutputFile ? [] : (resumed?.tweets ? [...resumed.tweets] : []);\n        const jsonlState = useOutputFile ? loadJsonlArchiveState(outputFile) : null;\n        const seen = useOutputFile\n            ? jsonlState.seen\n            : new Set(allTweets.map((tweet) => tweet?.id).filter(Boolean));\n        if (useOutputFile && resumed && jsonlState.count !== resumed.count) {\n            throw new CommandExecutionError(`Twitter bookmarks output file has ${jsonlState.count} record(s), expected resume count ${resumed.count}`);\n        }\n        let outputCount = useOutputFile ? jsonlState.count : 0;\n        let cursor = resumed?.cursor || null;\n        let pages = 0;\n        let exhausted = false;\n        // Runaway guard only; --limit/--all and cursor exhaustion control normal pagination.\n        while (pages < maxPages && (fetchAll || allTweets.length < limit)) {","sourceCodeStart":202,"sourceCodeEnd":238,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/twitter/bookmarks.js#L202-L238","documentation":"During a resumed archive run, the resume file records a nonzero count of records already written, but the JSONL output file it points to no longer exists on disk. clis/twitter/bookmarks.js:220 throws CommandExecutionError because continuing would produce an archive with a gap; the on-disk file must exist to match the resume state.","triggerScenarios":"Resume file has resumed.count > 0 and an outputFile recorded, but `fs.existsSync(outputFile)` is false when the run resumes — detected immediately before fetching.","commonSituations":"User deleted or moved the partial archive file but kept the resume file; output path changed between runs (different cwd, renamed file); cleanup job removed the .jsonl but not the .resume.json; output mismatch check was passed because paths resolve differently.","solutions":["Restore the output file to the recorded path (restore from backup/trash) and re-run with --all --output-file --resume-file.","Delete the stale resume file and start the archive from scratch (accepting re-fetch).","Pass the exact same --output-file path used in the original run so path resolution matches the resume record.","Check for typos or a changed working directory in the --output-file argument."],"exampleFix":"// before\nrm bookmarks.jsonl   # resume file left behind\ntwitter bookmarks --all --output-file bookmarks.jsonl --resume-file bookmarks.resume.json\n// after: start clean when archive is unrecoverable\nrm bookmarks.jsonl bookmarks.resume.json\ntwitter bookmarks --all --output-file bookmarks.jsonl --resume-file bookmarks.resume.json","handlingStrategy":"validation","validationCode":"const resume = JSON.parse(fs.readFileSync(resumeFile, 'utf8'));\nif (resume.count > 0 && resume.outputFile && !fs.existsSync(resume.outputFile)) {\n  throw new Error(`Output file missing for resume state: ${resume.outputFile}`);\n}","typeGuard":"function isResumableArchive(resume, outputFile) {\n  return !(resume && resume.count > 0) || (typeof outputFile === 'string' && fs.existsSync(outputFile));\n}","tryCatchPattern":"try {\n  await runBookmarks(argv);\n} catch (e) {\n  if (e instanceof CommandExecutionError && e.message.includes('output file is missing for resume state')) {\n    console.error('Restore the .jsonl archive or delete the stale .resume.json to start fresh.');\n    process.exitCode = 1;\n  } else throw e;\n}","preventionTips":["Never delete the .jsonl archive without also deleting its .resume.json (and vice versa).","Keep output and resume files in the same directory and move them together.","Run archiving with a stable working directory / absolute output paths.","Back up partial archives before cleanup jobs run."],"tags":["filesystem","resume-state","consistency"],"backgroundTag":"resume-state-mismatch","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}