jackwener/OpenCLI · error · CommandExecutionError

Could not persist Twitter bookmarks resume state: ${error in

Error message

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

What it means

writeResumeFile persists state atomically: it writes `${filePath}.tmp-${pid}` then renames onto the resume file. If the write or rename fails, it removes the temp file and throws this CommandExecutionError wrapping the underlying message (ENOENT, EACCES, ENOSPC, EPERM, etc.).

Source

Thrown at clis/twitter/bookmarks.js:164

// Keep this in sync with twitter/likes; 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 bookmarks resume state: ${error instanceof Error ? error.message : String(error)}`);
    }
}
cli({
    site: 'twitter',
    name: 'bookmarks',
    access: 'read',
    description: 'Fetch your Twitter/X bookmarks (the logged-in user\'s saved tweets, newest first)',
    domain: 'x.com',
    strategy: Strategy.COOKIE,
    browser: true,
    args: [
        { name: 'limit', type: 'int', default: 20, help: 'Maximum number of bookmarks to return (default 20). Ignored when --all is set.' },
        { name: 'all', type: 'bool', default: false, help: 'Fetch all bookmark pages until exhausted. Prefer --output-file for large archives.' },
        { name: 'resume-file', type: 'string', help: 'Resume file for long-running all-pages bookmark 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 bookmarks 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 (saved-time) ordering. Incompatible with --output-file.' },
    ],

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Check disk space (`df -h`) and free space if ENOSPC.
  2. Verify the resume-file's parent directory exists and is writable by the current user (mkdir/chown/chmod).
  3. Point --resume-file at a writable path (e.g. $HOME or a writable volume).
  4. If a stale `.tmp-<pid>` file exists and is locked, remove it and retry; on Windows, close tools holding the file.

Example fix

// before: fails
cli twitter bookmarks --all --resume-file /proc/run/b.json
// EACCES/ENOENT -> Could not persist Twitter bookmarks resume state
// after:
mkdir -p ~/var/lib/twitter-sync
cli twitter bookmarks --all --resume-file ~/var/lib/twitter-sync/bookmarks.json --output-file ~/var/lib/twitter-sync/archive.jsonl
Defensive patterns

Strategy: try-catch

Validate before calling

const dir = path.dirname(resumeFile);
fs.mkdirSync(dir, { recursive: true });
fs.accessSync(dir, fs.constants.W_OK);
// also check free space if the sync is large
const stat = fs.statfsSync(dir);
if (stat.bsize * stat.bavail < 10 * 1024 * 1024) throw new Error('low disk space');

Type guard

null

Try / catch

try {
  await run(['twitter', 'bookmarks', '--all', '--resume-file', p, '--output-file', o]);
} catch (e) {
  if (String(e.message).includes('Could not persist Twitter bookmarks resume state')) {
    console.error('resume write failed:', e.message);
    const writable = path.join(os.tmpdir(), 'bookmarks-resume.json');
    await run(['twitter', 'bookmarks', '--all', '--resume-file', writable, '--output-file', o]);
  } else throw e;
}

Prevention

When it happens

Trigger: Resume-file directory does not exist and cannot be created (permissions); disk full (ENOSPC); read-only filesystem; the target path is a directory; another process/AV locks the file during rename on some platforms.

Common situations: --resume-file pointing under a root-owned or read-only directory; container volumes mounted read-only; quota exceeded on a long --all sync; resume path inside a deleted working directory.

Related errors


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