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
- Truncate the .jsonl to exactly resumed.count records so file and resume state agree, then re-run.
- Discard both files and start a fresh archive (delete output + resume, re-run).
- Restore the .jsonl from a backup matching the resume count.
- 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
- Never hand-edit or trim the .jsonl archive between runs.
- Treat output+resume as one unit: back up, move, and delete them together.
- Do not share resume files across different archive runs or output files.
- Avoid killing the process mid-write; use graceful interruption so resume stays consistent.
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
- Twitter bookmarks output file is missing for resume state: $
- Bilibili creator comparison returned a malformed manuscript
- Bilibili creator comparison returned duplicate rows for ${bv
- Bilibili view API returned duplicate page entries for p=${pa
- Booking.com hotel row is missing stable name/url identity
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/586752eb5ca886a9.
Report an issue: GitHub.