jackwener/OpenCLI · error · CommandExecutionError

Could not persist Twitter likes resume state

Error message

Could not persist Twitter likes resume state: ${error instanceof Error ? error.message : String(error)}

What it means

writeResumeFile persists resume state atomically via a temporary file that is later renamed over the target. If any filesystem operation (write, rename, mkdir) fails, the temp file is removed and the original error message is wrapped in a CommandExecutionError with this prefix. It signals the run's progress could not be saved to disk and the underlying OS error is embedded in the message.

Solutions

  1. Read the embedded OS error after the colon and fix it — e.g. create the parent directory (mkdir -p) the resume file path expects.
  2. Check free disk space (df -h) and quotas; clear space if ENOSPC.
  3. Verify the volume is writable (permissions, not mounted read-only) and the user running the CLI can write there.
  4. Point --resume-file at a local writable path instead of a network mount, then move it after the run.

Example fix

// before
opencli twitter likes @jack --all --resume-file /nonexistent/dir/likes.json --output-file likes.ndjson
// after
mkdir -p ./resume && opencli twitter likes @jack --all --resume-file ./resume/likes.json --output-file likes.ndjson
Defensive patterns

Strategy: try-catch

Validate before calling

const dir = path.dirname(resumePath);
fs.mkdirSync(dir, { recursive: true });
fs.accessSync(dir, fs.constants.W_OK);

Try / catch

try {
  await runLikes({ resumeFile });
} catch (err) {
  if (/Could not persist Twitter likes resume state/.test(err.message)) {
    const cause = err.message.split(': ')[1];
    console.error('Resume persist failed:', cause); // e.g. ENOSPC, EACCES
    // fix disk/permissions, then resume from the last valid file
  }
}

Prevention

When it happens

Trigger: Calling --resume-file whose parent directory does not exist or is not writable; disk full; the temp file cannot be created/renamed due to permissions (read-only volume, EACCES, ENOSPC, EROFS); the path points at a directory.

Common situations: Typo in the resume file path's directory; running in a container with a read-only filesystem; disk quota exceeded on long full-archive scrapes; SMB/NFS mount dropped mid-run.

Understand the failure class

Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.

Related errors


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

Appendix: source

Thrown at clis/twitter/likes.js:182

// Keep this in sync with twitter/bookmarks; wording is command-specific, so the
// atomic tmp-write/rename cleanup sequence intentionally remains local.
function writeResumeFile(filePath, payload) {
    if (!filePath)
        return;
    ensureParentDir(filePath);
    const temporaryPath = `${filePath}.tmp-${process.pid}`;
    try {
        fs.writeFileSync(temporaryPath, JSON.stringify(payload, null, 2) + '\n');
        fs.renameSync(temporaryPath, filePath);
    }
    catch (error) {
        try {
            fs.rmSync(temporaryPath, { force: true });
        }
        catch {
        }
        throw new CommandExecutionError(`Could not persist Twitter likes resume state: ${error instanceof Error ? error.message : String(error)}`);
    }
}
cli({
    site: 'twitter',
    name: 'likes',
    access: 'read',
    description: 'Fetch liked tweets of a Twitter user (defaults to the logged-in user when no username is given)',
    domain: 'x.com',
    strategy: Strategy.COOKIE,
    browser: true,
    args: [
        { name: 'username', type: 'string', positional: true, help: 'Twitter screen name (with or without @). Defaults to the logged-in user when omitted.' },
        { name: 'limit', type: 'int', default: 20, help: 'Maximum number of liked tweets to return (default 20). Ignored when --all is set.' },
        { name: 'all', type: 'bool', default: false, help: 'Fetch all liked-tweet pages until exhausted. Prefer --output-file for large archives.' },
        { name: 'resume-file', type: 'string', help: 'Resume file for long-running all-pages likes syncs.' },
        { name: 'output-file', type: 'string', help: 'Write all-page results to JSONL. Requires --all and --resume-file.' },
        { name: 'max-pages', type: 'int', help: `Optional pagination safety cap (default ${DEFAULT_MAX_PAGINATION_PAGES}; raised automatically with --all).` },
        { name: 'top-by-engagement', type: 'int', default: 0, help: 'When set to N>0, re-rank the liked tweets by weighted engagement (likes×1 + retweets×3 + replies×2 + bookmarks×5 + log10(views+1)×0.5) and return the top N. Default 0 keeps the API\'s native (recency) ordering. Incompatible with --output-file.' },

View on GitHub (pinned to 49907e53dc)