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

  1. Point --output-file at a new file path if you intend a fresh archive.
  2. Resume the previous run instead: pass the matching --resume-file alongside the existing --output-file.
  3. Explicitly delete/rename the existing output file if you are certain it should be overwritten.
  4. 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

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


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/2d4ed19f11a9eb45. Report an issue: GitHub.