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

  1. Compare the file against the expected schema: {count: integer>=0, cursor?: string, source, username, outputFile?, tweets?} and fix the fields
  2. Delete the invalid file and start the likes run fresh, letting the library write a new resume file
  3. Ensure you are pointing --resume at the library-generated resume file, not another JSON artifact
  4. 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

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


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/3c28b17ebf021dcf. Report an issue: GitHub.