jackwener/OpenCLI · error · CommandExecutionError

Twitter bookmarks output file has ${jsonlState.count} record

Error message

Twitter bookmarks output file has ${jsonlState.count} record(s), expected resume count ${resumed.count}

What it means

When resuming an archive, the record count stored in the resume file must match the number of records actually present in the JSONL output file. clis/twitter/bookmarks.js:231 throws CommandExecutionError when jsonlState.count !== resumed.count, indicating the archive file and resume state have diverged and continuing would corrupt the archive.

Source

Thrown at clis/twitter/bookmarks.js:231

            '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);
            const fetchCount = Math.min(100, remaining);
            const apiUrl = buildBookmarksUrl(fetchCount, cursor).replace(BOOKMARKS_QUERY_ID, queryId);
            const data = unwrapBrowserResult(await page.evaluate(`async () => {
        const r = await fetch(${JSON.stringify(apiUrl)}, { headers: ${headers}, credentials: 'include' });
        return r.ok ? await r.json() : { error: r.status };
      }`));
            if (data?.error) {
                if ((useOutputFile ? outputCount : allTweets.length) === 0)

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Truncate the .jsonl to exactly resumed.count records so file and resume state agree, then re-run.
  2. Discard both files and start a fresh archive (delete output + resume, re-run).
  3. Restore the .jsonl from a backup matching the resume count.
  4. Do not hand-edit JSONL archives or share resume files across runs.

Example fix

// before: hand-trimmed archive diverges from resume count
# wc -l bookmarks.jsonl  -> 120; resume says 125
// after: truncate to resume count then resume
head -n 125 bookmarks.jsonl > tmp && mv tmp bookmarks.jsonl
Defensive patterns

Strategy: validation

Validate before calling

const resume = JSON.parse(fs.readFileSync(resumeFile, 'utf8'));
const fileCount = fs.readFileSync(outputFile, 'utf8').split('\n').filter(Boolean).length;
if (fileCount !== resume.count) {
  throw new Error(`Archive/resume mismatch: file has ${fileCount}, resume expects ${resume.count}`);
}

Type guard

function archiveMatchesResume(resume, jsonlState) {
  return !resume || !jsonlState || jsonlState.count === resume.count;
}

Try / catch

try {
  await runBookmarks(argv);
} catch (e) {
  if (e instanceof CommandExecutionError && e.message.includes('expected resume count')) {
    console.error('Truncate the .jsonl to the resume count or delete both files and start fresh.');
    process.exitCode = 1;
  } else throw e;
}

Prevention

When it happens

Trigger: Resume run with --output-file where loadJsonlArchiveState(outputFile).count differs from resumed.count — e.g. the jsonl was truncated, edited, or appended to outside the CLI, or resume/jsonl pair got out of sync after a crash mid-write.

Common situations: Manually editing or trimming the .jsonl archive; a crash between writing a JSONL row and updating the resume file; swapping resume files between two archive runs; an external process appending rows.

Related errors


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