jackwener/OpenCLI · error · CommandExecutionError
Could not persist Twitter bookmarks resume state
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.).
Solutions
- Check disk space (`df -h`) and free space if ENOSPC.
- Verify the resume-file's parent directory exists and is writable by the current user (mkdir/chown/chmod).
- Point --resume-file at a writable path (e.g. $HOME or a writable volume).
- 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
- Point --resume-file at a writable, non-volatile directory ($HOME, writable volume).
- Monitor disk space before long --all syncs.
- Avoid read-only mounts and root-owned paths for state files.
- Clean stale `.tmp-<pid>` files after crashes.
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.
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
- Could not persist Twitter likes resume state
- antigravity workspaces-list: No workspace storage.
- Browser reported a completed download but the file is…
- Could not find package.json above
- Could not parse Twitter bookmarks resume file
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/fff4b72b3da0acf0.
Report an issue: GitHub.
Appendix: 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)