jackwener/OpenCLI · error · CommandExecutionError
Could not parse Twitter bookmarks resume file ${filePath}: $
Error message
Could not parse Twitter bookmarks resume file ${filePath}: ${error instanceof Error ? error.message : String(error)} What it means
readResumeFile JSON-parses the resume file pointed to by --resume-file; if JSON.parse or the read throws, it wraps the underlying parse/read message in a CommandExecutionError. This means the resume file exists but is not valid JSON (or is unreadable), so the bookmarks sync cannot restore its prior state.
Source
Thrown at clis/twitter/bookmarks.js:113
}
for (const item of content?.items || []) {
const nested = extractBookmarkTweet(item.item?.itemContent?.tweet_results?.result, seen);
if (nested)
tweets.push(nested);
}
}
}
return { tweets, nextCursor };
}
function readResumeFile(filePath, expected = null) {
if (!filePath || !fs.existsSync(filePath))
return null;
let parsed;
try {
parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
}
catch (error) {
throw new CommandExecutionError(`Could not parse Twitter bookmarks resume file ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
}
const count = parsed?.count;
const cursor = parsed?.cursor == null ? null : String(parsed.cursor);
const outputFile = parsed?.outputFile ? path.resolve(String(parsed.outputFile)) : null;
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)
|| !Number.isInteger(count) || count < 0
|| (parsed.cursor != null && typeof parsed.cursor !== 'string')
|| (cursor !== null && !cursor.trim())) {
throw new CommandExecutionError(`Twitter bookmarks resume file ${filePath} has an invalid shape`);
}
if (expected) {
if (parsed.source !== expected.source)
throw new ArgumentError(`Resume file source mismatch: expected ${expected.source}, found ${parsed.source || 'unknown'}`);
if (outputFile !== expected.outputFile)
throw new ArgumentError(`Resume file output mismatch: expected ${expected.outputFile || 'in-memory mode'}, found ${outputFile || 'in-memory mode'}`);
if (!expected.outputFile && !Array.isArray(parsed.tweets))
throw new CommandExecutionError(`Twitter bookmarks resume file ${filePath} is missing in-memory tweets`);
if (!expected.outputFile && parsed.tweets.length !== count)View on GitHub (pinned to 49907e53dc)
Solutions
- Delete or rename the corrupt resume file (and its .tmp-* sibling) and restart the --all sync from scratch, or remove --resume-file to run in-memory.
- Validate the file with `JSON.parse(fs.readFileSync(path,'utf8'))` in node to see the exact parse error before retrying.
- Restore the file from a backup taken mid-sync.
- Check file permissions (readable by the current user) and encoding (UTF-8, no BOM).
Example fix
// before: resuming with a corrupt file
$ cli twitter bookmarks --all --resume-file ./bookmarks-resume.json
// CommandExecutionError: Could not parse ... Unexpected end of JSON input
// after: inspect, then restart clean
$ node -e "JSON.parse(require('fs').readFileSync('./bookmarks-resume.json','utf8'))"
$ rm ./bookmarks-resume.json && cli twitter bookmarks --all --resume-file ./bookmarks-resume.json Defensive patterns
Strategy: validation
Validate before calling
function isParsableJsonResumeFile(p) {
try {
JSON.parse(require('node:fs').readFileSync(p, 'utf8'));
return true;
} catch {
return false;
}
}
if (resumeFile && fs.existsSync(resumeFile) && !isParsableJsonResumeFile(resumeFile)) {
fs.rmSync(resumeFile, { force: true }); // start fresh
} Type guard
function isValidResumeJson(value) {
return typeof value === 'object' && value !== null && !Array.isArray(value);
} Try / catch
try {
await run(['twitter', 'bookmarks', '--all', '--resume-file', resumePath]);
} catch (e) {
if (String(e.message).includes('Could not parse Twitter bookmarks resume file')) {
fs.rmSync(resumePath, { force: true });
await run(['twitter', 'bookmarks', '--all', '--resume-file', resumePath]);
} else throw e;
} Prevention
- Never hand-edit resume files; let the tool write them atomically.
- Keep resume files on the same filesystem as the tool so rename-based writes stay atomic.
- Validate JSON before resuming an unattended sync.
- Keep backups of long-running sync state.
When it happens
Trigger: Running `twitter bookmarks --all --resume-file <path>` where the file exists but contains truncated JSON (e.g. an interrupted non-atomic write), garbage/binary content, BOM corruption, or a read-permission/encoding failure.
Common situations: Manually editing the resume file and leaving invalid JSON; a previous crash corrupted it; a tool wrote partial content; the file was replaced by a symlink or a file with no read permission.
Related errors
- DuckDuckGo suggest returned malformed JSON: ${err?.message ?
- ${label} returned non-JSON body: ${err.message}
- Twitter bookmarks resume file ${filePath} has an invalid sha
- Twitter bookmarks resume file ${filePath} is missing in-memo
- Twitter bookmarks resume file ${filePath} count does not mat
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/d7de56c20ef16aef.
Report an issue: GitHub.