jackwener/OpenCLI · error · CommandExecutionError
Twitter likes resume file ${filePath} has an invalid shape
Error message
Twitter likes resume file ${filePath} has an invalid shape What it means
readResumeFile validates the parsed resume file's structure and throws this CommandExecutionError when it parses as JSON but does not match the expected schema: it must be a non-array object with an integer count >= 0, an optional string cursor, and (if present) a cursor that is non-blank. This guards against resuming from a semantically invalid file.
Source
Thrown at clis/twitter/likes.js:137
}
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 likes 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 likes 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 (String(parsed.username || '').toLowerCase() !== String(expected.username).toLowerCase())
throw new ArgumentError(`Resume file username mismatch: expected @${expected.username}, found @${parsed.username || '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 likes resume file ${filePath} is missing in-memory tweets`);
if (!expected.outputFile && parsed.tweets.length !== count)
throw new CommandExecutionError(`Twitter likes resume file ${filePath} count does not match its in-memory tweets`);
if (parsed.complete)
throw new CommandExecutionError(`Twitter likes resume file ${filePath} is already marked complete`);
}
return {
cursor,
count,View on GitHub (pinned to 49907e53dc)
Solutions
- Compare the file against the expected schema: {count: integer>=0, cursor?: string, source, username, outputFile?, tweets?} and fix the fields
- Delete the invalid file and start the likes run fresh, letting the library write a new resume file
- Ensure you are pointing --resume at the library-generated resume file, not another JSON artifact
- Check for library version mismatch — regenerate the resume with the same version that will resume it
Example fix
// before (invalid shape)
{"count": "42", "cursor": ""}
// after
{"count": 42, "cursor": "abc123cursor", "source": "likes", "username": "someuser"} Defensive patterns
Strategy: validation
Validate before calling
function isValidResumeShape(data) {
return data && typeof data === 'object' && !Array.isArray(data)
&& Number.isInteger(data.count) && data.count >= 0
&& (data.cursor == null || (typeof data.cursor === 'string' && data.cursor.trim() !== ''));
}
const parsed = JSON.parse(fs.readFileSync(resumePath, 'utf8'));
if (!isValidResumeShape(parsed)) throw new Error('Invalid resume shape before resuming'); Type guard
function isResumeFile(v) {
return typeof v === 'object' && v !== null && !Array.isArray(v)
&& Number.isInteger(v.count) && v.count >= 0
&& (v.cursor == null || typeof v.cursor === 'string');
} Try / catch
try {
await runLikes({ resume: resumePath });
} catch (e) {
if (/has an invalid shape/.test(e.message)) {
fs.rmSync(resumePath);
return runLikes({}); // restart fresh
}
throw e;
} Prevention
- Never hand-edit resume files; regenerate them via the library
- Point --resume only at files the library itself wrote
- Keep the same library version for the run that writes and the run that resumes
- Validate shape (count integer, cursor string) before resuming
When it happens
Trigger: Resuming `twitter likes` with a file whose parsed JSON lacks required/valid fields — e.g. count is missing, non-integer, or negative; cursor is present but not a string; cursor is an empty/whitespace string; the JSON root is an array or null.
Common situations: Hand-edited resume files with wrong types (count as string "42"); a resume file generated by a different tool or library version with a different schema; accidentally pointing --resume at an unrelated JSON file (like a package.json or output data file).
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- Twitter likes resume file ${filePath} is missing in-memory t
- Could not parse Twitter likes resume file ${filePath}: ${err
- Bilibili conclusion API returned malformed model_result
- ${label} returned a malformed article id
- Twitter bookmarks resume file ${filePath} has an invalid sha
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/3c28b17ebf021dcf.
Report an issue: GitHub.