{"record":{"id":"586752eb5ca886a9","repo":"jackwener/OpenCLI","slug":"twitter-bookmarks-output-file-has-jsonlstate-cou","errorCode":null,"errorMessage":"Twitter bookmarks output file has ${jsonlState.count} record(s), expected resume count ${resumed.count}","messagePattern":"Twitter bookmarks output file has (.+?) record\\(s\\), expected resume count (.+?)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/twitter/bookmarks.js","lineNumber":231,"sourceCode":"            'X-Twitter-Active-User': 'yes',\n        });\n        const resumed = fetchAll ? readResumeFile(resumeFile, {\n            source: 'bookmarks',\n            outputFile: useOutputFile ? outputFile : null,\n        }) : null;\n        if (useOutputFile && resumed && resumed.count > 0 && !fs.existsSync(outputFile)) {\n            throw new CommandExecutionError(`Twitter bookmarks output file is missing for resume state: ${outputFile}`);\n        }\n        if (useOutputFile && !resumed && fs.existsSync(outputFile)) {\n            throw new ArgumentError(`Refusing to overwrite existing Twitter bookmarks output file: ${outputFile}`);\n        }\n        const allTweets = useOutputFile ? [] : (resumed?.tweets ? [...resumed.tweets] : []);\n        const jsonlState = useOutputFile ? loadJsonlArchiveState(outputFile) : null;\n        const seen = useOutputFile\n            ? jsonlState.seen\n            : new Set(allTweets.map((tweet) => tweet?.id).filter(Boolean));\n        if (useOutputFile && resumed && jsonlState.count !== resumed.count) {\n            throw new CommandExecutionError(`Twitter bookmarks output file has ${jsonlState.count} record(s), expected resume count ${resumed.count}`);\n        }\n        let outputCount = useOutputFile ? jsonlState.count : 0;\n        let cursor = resumed?.cursor || null;\n        let pages = 0;\n        let exhausted = false;\n        // Runaway guard only; --limit/--all and cursor exhaustion control normal pagination.\n        while (pages < maxPages && (fetchAll || allTweets.length < limit)) {\n            pages += 1;\n            const currentCount = useOutputFile ? outputCount : allTweets.length;\n            const remaining = fetchAll ? 100 : (limit - currentCount + 10);\n            const fetchCount = Math.min(100, remaining);\n            const apiUrl = buildBookmarksUrl(fetchCount, cursor).replace(BOOKMARKS_QUERY_ID, queryId);\n            const data = unwrapBrowserResult(await page.evaluate(`async () => {\n        const r = await fetch(${JSON.stringify(apiUrl)}, { headers: ${headers}, credentials: 'include' });\n        return r.ok ? await r.json() : { error: r.status };\n      }`));\n            if (data?.error) {\n                if ((useOutputFile ? outputCount : allTweets.length) === 0)","sourceCodeStart":213,"sourceCodeEnd":249,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/twitter/bookmarks.js#L213-L249","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: hand-trimmed archive diverges from resume count\n# wc -l bookmarks.jsonl  -> 120; resume says 125\n// after: truncate to resume count then resume\nhead -n 125 bookmarks.jsonl > tmp && mv tmp bookmarks.jsonl","handlingStrategy":"validation","validationCode":"const resume = JSON.parse(fs.readFileSync(resumeFile, 'utf8'));\nconst fileCount = fs.readFileSync(outputFile, 'utf8').split('\\n').filter(Boolean).length;\nif (fileCount !== resume.count) {\n  throw new Error(`Archive/resume mismatch: file has ${fileCount}, resume expects ${resume.count}`);\n}","typeGuard":"function archiveMatchesResume(resume, jsonlState) {\n  return !resume || !jsonlState || jsonlState.count === resume.count;\n}","tryCatchPattern":"try {\n  await runBookmarks(argv);\n} catch (e) {\n  if (e instanceof CommandExecutionError && e.message.includes('expected resume count')) {\n    console.error('Truncate the .jsonl to the resume count or delete both files and start fresh.');\n    process.exitCode = 1;\n  } else throw e;\n}","preventionTips":["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."],"tags":["resume-state","consistency","data-integrity"],"backgroundTag":"resume-state-mismatch","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}