jackwener/OpenCLI · error · CommandExecutionError
Twitter bookmarks output file is missing for resume state: $
Error message
Twitter bookmarks output file is missing for resume state: ${outputFile} What it means
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.
Source
Thrown at clis/twitter/bookmarks.js:220
throw new ArgumentError('--output-file requires --resume-file so partial archives remain resumable');
}
const cookies = await page.getCookies({ url: 'https://x.com' });
const ct0 = cookies.find((c) => c.name === 'ct0')?.value || null;
if (!ct0)
throw new AuthRequiredError('x.com', 'Not logged into x.com (no ct0 cookie)');
const queryId = await resolveTwitterQueryId(page, 'Bookmarks', BOOKMARKS_QUERY_ID);
const headers = JSON.stringify({
'Authorization': `Bearer ${decodeURIComponent(TWITTER_BEARER_TOKEN)}`,
'X-Csrf-Token': ct0,
'X-Twitter-Auth-Type': 'OAuth2Session',
'X-Twitter-Active-User': 'yes',
});
const resumed = fetchAll ? readResumeFile(resumeFile, {
source: 'bookmarks',
outputFile: useOutputFile ? outputFile : null,
}) : null;
if (useOutputFile && resumed && resumed.count > 0 && !fs.existsSync(outputFile)) {
throw new CommandExecutionError(`Twitter bookmarks output file is missing for resume state: ${outputFile}`);
}
if (useOutputFile && !resumed && fs.existsSync(outputFile)) {
throw new ArgumentError(`Refusing to overwrite existing Twitter bookmarks output file: ${outputFile}`);
}
const allTweets = useOutputFile ? [] : (resumed?.tweets ? [...resumed.tweets] : []);
const jsonlState = useOutputFile ? loadJsonlArchiveState(outputFile) : null;
const seen = useOutputFile
? jsonlState.seen
: new Set(allTweets.map((tweet) => tweet?.id).filter(Boolean));
if (useOutputFile && resumed && jsonlState.count !== resumed.count) {
throw new CommandExecutionError(`Twitter bookmarks output file has ${jsonlState.count} record(s), expected resume count ${resumed.count}`);
}
let outputCount = useOutputFile ? jsonlState.count : 0;
let cursor = resumed?.cursor || null;
let pages = 0;
let exhausted = false;
// Runaway guard only; --limit/--all and cursor exhaustion control normal pagination.
while (pages < maxPages && (fetchAll || allTweets.length < limit)) {View on GitHub (pinned to 49907e53dc)
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.
Example fix
// before rm bookmarks.jsonl # resume file left behind twitter bookmarks --all --output-file bookmarks.jsonl --resume-file bookmarks.resume.json // after: start clean when archive is unrecoverable rm bookmarks.jsonl bookmarks.resume.json twitter bookmarks --all --output-file bookmarks.jsonl --resume-file bookmarks.resume.json
Defensive patterns
Strategy: validation
Validate before calling
const resume = JSON.parse(fs.readFileSync(resumeFile, 'utf8'));
if (resume.count > 0 && resume.outputFile && !fs.existsSync(resume.outputFile)) {
throw new Error(`Output file missing for resume state: ${resume.outputFile}`);
} Type guard
function isResumableArchive(resume, outputFile) {
return !(resume && resume.count > 0) || (typeof outputFile === 'string' && fs.existsSync(outputFile));
} Try / catch
try {
await runBookmarks(argv);
} catch (e) {
if (e instanceof CommandExecutionError && e.message.includes('output file is missing for resume state')) {
console.error('Restore the .jsonl archive or delete the stale .resume.json to start fresh.');
process.exitCode = 1;
} else throw e;
} Prevention
- 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.
When it happens
Trigger: Resume file has resumed.count > 0 and an outputFile recorded, but `fs.existsSync(outputFile)` is false when the run resumes — detected immediately before fetching.
Common situations: 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.
Related errors
- Twitter bookmarks output file has ${jsonlState.count} record
- Verify command returned no metric for baseline
- File not found: ${path}
- File must be a readable text file: ${path}
- File could not be read: ${path}
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/67535bac922d70a8.
Report an issue: GitHub.