{"record":{"id":"9f952707b5182f38","repo":"jackwener/OpenCLI","slug":"resume-file-output-mismatch-expected-expected-o","errorCode":null,"errorMessage":"Resume file output mismatch: expected ${expected.outputFile || 'in-memory mode'}, found ${outputFile || 'in-memory mode'}","messagePattern":"Resume file output mismatch: expected (.+?), found (.+?)","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/twitter/bookmarks.js","lineNumber":128,"sourceCode":"        parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));\n    }\n    catch (error) {\n        throw new CommandExecutionError(`Could not parse Twitter bookmarks resume file ${filePath}: ${error instanceof Error ? error.message : String(error)}`);\n    }\n    const count = parsed?.count;\n    const cursor = parsed?.cursor == null ? null : String(parsed.cursor);\n    const outputFile = parsed?.outputFile ? path.resolve(String(parsed.outputFile)) : null;\n    if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)\n        || !Number.isInteger(count) || count < 0\n        || (parsed.cursor != null && typeof parsed.cursor !== 'string')\n        || (cursor !== null && !cursor.trim())) {\n        throw new CommandExecutionError(`Twitter bookmarks resume file ${filePath} has an invalid shape`);\n    }\n    if (expected) {\n        if (parsed.source !== expected.source)\n            throw new ArgumentError(`Resume file source mismatch: expected ${expected.source}, found ${parsed.source || 'unknown'}`);\n        if (outputFile !== expected.outputFile)\n            throw new ArgumentError(`Resume file output mismatch: expected ${expected.outputFile || 'in-memory mode'}, found ${outputFile || 'in-memory mode'}`);\n        if (!expected.outputFile && !Array.isArray(parsed.tweets))\n            throw new CommandExecutionError(`Twitter bookmarks resume file ${filePath} is missing in-memory tweets`);\n        if (!expected.outputFile && parsed.tweets.length !== count)\n            throw new CommandExecutionError(`Twitter bookmarks resume file ${filePath} count does not match its in-memory tweets`);\n        if (parsed.complete)\n            throw new CommandExecutionError(`Twitter bookmarks resume file ${filePath} is already marked complete`);\n    }\n    return {\n        cursor,\n        count,\n        tweets: Array.isArray(parsed.tweets) ? parsed.tweets : [],\n        complete: Boolean(parsed.complete),\n        source: parsed.source || null,\n        outputFile,\n        updatedAt: parsed.updatedAt || null,\n    };\n}\n","sourceCodeStart":110,"sourceCodeEnd":146,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/twitter/bookmarks.js#L110-L146","documentation":"The command compares the resume file's resolved `outputFile` against the expected mode of the current run. If the stored state was produced with --output-file but the current run is in-memory (or vice versa, or a different path), readResumeFile throws this ArgumentError because the archive and resume state must agree.","triggerScenarios":"Resuming with `--all --resume-file f` but omitting the --output-file that the original run used (or the reverse); passing a different/renamed --output-file path than the one recorded in the resume file; the resume file's outputFile was hand-edited.","commonSituations":"Moving the JSONL archive to another directory without updating the resume file; dropping --output-file on resume because 'the data is already in the archive'; relative vs absolute path confusion (the file stores a path.resolve'd absolute path).","solutions":["Re-run with the exact same --output-file (absolute path) that the resume file records.","Delete the resume file and restart the sync with the desired mode.","If intentionally switching modes, start over — never reuse a resume file across modes."],"exampleFix":"// before (original run used --output-file):\ncli twitter bookmarks --all --resume-file ./b.json   # in-memory resume -> mismatch\n// after:\ncli twitter bookmarks --all --resume-file ./b.json --output-file /abs/path/archive.jsonl","handlingStrategy":"validation","validationCode":"const parsed = JSON.parse(fs.readFileSync(resumeFile, 'utf8'));\nconst recorded = parsed.outputFile ? path.resolve(parsed.outputFile) : null;\nif (Boolean(recorded) !== Boolean(outputFile) || (recorded && outputFile && recorded !== path.resolve(outputFile))) {\n  throw new Error('resume/output-file mode mismatch');\n}","typeGuard":"function outputModeMatches(parsed, expectedOutputFile) {\n  const recorded = parsed.outputFile ? path.resolve(String(parsed.outputFile)) : null;\n  return recorded === (expectedOutputFile ? path.resolve(expectedOutputFile) : null);\n}","tryCatchPattern":"try {\n  await run(args);\n} catch (e) {\n  if (String(e.message).includes('Resume file output mismatch')) {\n    const parsed = JSON.parse(fs.readFileSync(resumeFile, 'utf8'));\n    if (parsed.outputFile) args.push('--output-file', path.resolve(parsed.outputFile));\n    await run(args);\n  } else throw e;\n}","preventionTips":["Store and pass absolute --output-file paths; the resume file records path.resolve'd paths.","Never toggle between in-memory and --output-file mode mid-sync.","If you move the archive, update or recreate the resume file.","Script the whole flag set together so flags never drift apart."],"tags":["argument-error","resume-file","mismatch"],"backgroundTag":"resume-file-output-mismatch","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}