jackwener/OpenCLI · error · ArgumentError
Refusing to overwrite existing Twitter bookmarks output file
Error message
Refusing to overwrite existing Twitter bookmarks output file: ${outputFile} What it means
To avoid destroying existing archive data, the CLI refuses to start a fresh (non-resumed) archive run when the output file already exists. clis/twitter/bookmarks.js:223 throws ArgumentError when useOutputFile is true, there is no valid resume state (resumed is null), and fs.existsSync(outputFile) is true.
Source
Thrown at clis/twitter/bookmarks.js:223
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)) {
pages += 1;
const currentCount = useOutputFile ? outputCount : allTweets.length;
const remaining = fetchAll ? 100 : (limit - currentCount + 10);View on GitHub (pinned to 49907e53dc)
Solutions
- Point --output-file at a new file path if you intend a fresh archive.
- Resume the previous run instead: pass the matching --resume-file alongside the existing --output-file.
- Explicitly delete/rename the existing output file if you are certain it should be overwritten.
- Keep the resume file until the archive completes (it is auto-removed on success) so reruns resume rather than clash.
Example fix
// before twitter bookmarks --all --output-file bookmarks.jsonl --resume-file fresh.resume.json # fresh run, file exists // after twitter bookmarks --all --output-file bookmarks.jsonl --resume-file bookmarks.resume.json # matching resume
Defensive patterns
Strategy: validation
Validate before calling
if (!fs.existsSync(resumeFile) && fs.existsSync(outputFile)) {
throw new Error(`Refusing to overwrite existing output file: ${outputFile} — resume first or choose a new path`);
} Type guard
function isSafeFreshArchiveTarget(resumeFile, outputFile) {
return fs.existsSync(resumeFile) || !fs.existsSync(outputFile);
} Try / catch
try {
await runBookmarks(argv);
} catch (e) {
if (e instanceof ArgumentError && e.message.includes('Refusing to overwrite')) {
console.error('Pick a new --output-file or supply the matching --resume-file to continue.');
process.exitCode = 2;
} else throw e;
} Prevention
- Keep the resume file until the archive completes; it is auto-removed on success.
- Use unique, dated output filenames for fresh archives.
- Check fs.existsSync(outputFile) in wrappers before a fresh run.
- Never reuse an --output-file path across different archive projects.
When it happens
Trigger: Running `twitter bookmarks --all --output-file existing.jsonl --resume-file new.resume.json` where the resume file does not exist (fresh run) but the output file does.
Common situations: Re-running a completed archive without the resume file (resume was removed on success); pointing --output-file at a different command's file; switching output filenames mid-workflow so old resume no longer matches.
Related errors
- File not found: ${path}
- File must be a readable text file: ${path}
- File could not be read: ${path}
- target appeared during reservation
- MiniMax music cannot reserve output file ${target}: ${error?
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/2d4ed19f11a9eb45.
Report an issue: GitHub.