{"record":{"id":"89c26de97ae53fc7","repo":"jackwener/OpenCLI","slug":"output-file-requires-resume-file-so-partial-ar","errorCode":null,"errorMessage":"--output-file requires --resume-file so partial archives remain resumable","messagePattern":"--output-file requires --resume-file so partial archives remain resumable","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/twitter/bookmarks.js","lineNumber":202,"sourceCode":"    func: async (page, kwargs) => {\n        const fetchAll = Boolean(kwargs.all);\n        const limit = fetchAll ? Number.POSITIVE_INFINITY : (kwargs.limit || 20);\n        const resumeFile = resolveOptionalFilePath(kwargs['resume-file'], '--resume-file');\n        const outputFile = resolveOptionalFilePath(kwargs['output-file'], '--output-file');\n        const useOutputFile = Boolean(fetchAll && outputFile);\n        const maxPages = resolveMaxPages(kwargs, fetchAll);\n        const topByEngagement = Number(kwargs['top-by-engagement'] || 0);\n        if (useOutputFile && topByEngagement > 0) {\n            throw new ArgumentError('--top-by-engagement cannot be combined with --output-file');\n        }\n        if (outputFile && !fetchAll) {\n            throw new ArgumentError('--output-file requires --all');\n        }\n        if (resumeFile && !fetchAll) {\n            throw new ArgumentError('--resume-file requires --all');\n        }\n        if (outputFile && !resumeFile) {\n            throw new ArgumentError('--output-file requires --resume-file so partial archives remain resumable');\n        }\n        const cookies = await page.getCookies({ url: 'https://x.com' });\n        const ct0 = cookies.find((c) => c.name === 'ct0')?.value || null;\n        if (!ct0)\n            throw new AuthRequiredError('x.com', 'Not logged into x.com (no ct0 cookie)');\n        const queryId = await resolveTwitterQueryId(page, 'Bookmarks', BOOKMARKS_QUERY_ID);\n        const headers = JSON.stringify({\n            'Authorization': `Bearer ${decodeURIComponent(TWITTER_BEARER_TOKEN)}`,\n            'X-Csrf-Token': ct0,\n            'X-Twitter-Auth-Type': 'OAuth2Session',\n            '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}`);","sourceCodeStart":184,"sourceCodeEnd":220,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/twitter/bookmarks.js#L184-L220","documentation":"When archiving to a JSONL output file, a matching --resume-file is mandatory so a partially written archive can be continued. clis/twitter/bookmarks.js:202 throws ArgumentError when --output-file is given without --resume-file. Without resume state, an interrupted run would leave an unresumable partial archive.","triggerScenarios":"Running `twitter bookmarks --all --output-file archive.jsonl` without `--resume-file`. Pure flag check in the argument validation block, before cookies/network access.","commonSituations":"Users adding --output-file to an existing command line but omitting --resume-file; automations that build flags conditionally and skip the resume flag; misunderstanding that output-file alone is sufficient for archiving.","solutions":["Add --resume-file pointing to the state file (e.g. archive.resume.json) alongside --output-file.","Remove --output-file if you want an in-memory fetch that tolerates interruption via the resume file alone.","Wrap both flags in a single script/alias so they are never separated."],"exampleFix":"// before\ntwitter bookmarks --all --output-file bookmarks.jsonl\n// after\ntwitter bookmarks --all --output-file bookmarks.jsonl --resume-file bookmarks.resume.json","handlingStrategy":"validation","validationCode":"if (has('--output-file') && !has('--resume-file')) {\n  throw new Error('--output-file requires --resume-file so partial archives remain resumable');\n}","typeGuard":"function canArchiveToOutput(opts) {\n  return typeof opts.outputFile === 'string' && typeof opts.resumeFile === 'string';\n}","tryCatchPattern":"try {\n  await runBookmarks(argv);\n} catch (e) {\n  if (e instanceof ArgumentError && e.message.includes('requires --resume-file')) {\n    console.error('Add --resume-file <state.json> alongside --output-file.');\n    process.exitCode = 2;\n  } else throw e;\n}","preventionTips":["Treat --output-file and --resume-file as an atomic flag pair in scripts.","Centralize archive invocations in one helper that always emits both flags.","Document the pairing requirement in team runbooks."],"tags":["cli","argument-validation","resumability"],"backgroundTag":"incompatible-cli-flags","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}