jackwener/OpenCLI · error · CommandExecutionError
Failed to write Pixiv novel ${path.basename(plan.destPath)}:
Error message
Failed to write Pixiv novel ${path.basename(plan.destPath)}: ${error?.message || error} What it means
commitNovelFile performs the actual mkdir/write (mkdirSync + openSync with 'wx' flag) and rolls back on failure. Any error that is not already a CommandExecutionError (e.g. ENOSPC disk full, EACCES, EMFILE, EIO) is wrapped in a CommandExecutionError prefixed with 'Failed to write Pixiv novel <basename>' plus the underlying OS message.
Source
Thrown at clis/pixiv/novel-download-utils.js:198
export function commitNovelFile(plan) {
let descriptor;
try {
fs.mkdirSync(plan.outputDir, { recursive: true });
descriptor = fs.openSync(plan.destPath, 'wx');
fs.writeFileSync(descriptor, plan.content, 'utf8');
fs.closeSync(descriptor);
descriptor = undefined;
return plan.destPath;
} catch (error) {
if (descriptor !== undefined) {
try { fs.closeSync(descriptor); } catch {}
try { fs.rmSync(plan.destPath, { force: true }); } catch {}
}
for (const directory of plan.createdDirs) {
try { fs.rmdirSync(directory); } catch {}
}
if (error instanceof CommandExecutionError) throw error;
throw new CommandExecutionError(`Failed to write Pixiv novel ${path.basename(plan.destPath)}: ${error?.message || error}`);
}
}
export function cleanupNovelFile(plan) {
try { fs.rmSync(plan.destPath, { force: true }); } catch {}
for (const directory of plan.createdDirs) {
try { fs.rmdirSync(directory); } catch {}
}
}
View on GitHub (pinned to 49907e53dc)
Solutions
- Read error.message in the thrown CommandExecutionError to identify the OS cause (ENOSPC -> free disk space; EACCES -> fix permissions; EMFILE -> raise ulimit -n or reduce concurrency).
- Free disk space / raise quota, then re-run after running cleanupNovelFile to remove partial artifacts.
- Ensure only one process downloads into the same output directory to avoid 'wx' EEXIST races.
- Reduce parallelism for large batches; retry the download once the transient cause (mount, space, fds) is fixed.
Example fix
// before await Promise.all(ids.map(id => writeNovelFile(bodies[id], out))); // EMFILE // after import pLimit from 'p-limit'; const limit = pLimit(4); await Promise.all(ids.map(id => limit(() => writeNovelFile(bodies[id], out))));
Defensive patterns
Strategy: try-catch
Validate before calling
import fs from 'node:fs';
function assertWritable(dir) {
fs.mkdirSync(dir, { recursive: true });
fs.accessSync(dir, fs.constants.W_OK);
const stat = fs.statfsSync(dir);
if (stat.bavail * stat.bsize < 10 * 1024 * 1024) throw new Error('Less than 10MB free');
} Try / catch
try {
await downloadNovel(id, { output });
} catch (e) {
if (e.message.startsWith('Failed to write Pixiv novel')) {
console.error(`Write failed: ${e.message}`);
if (e.message.includes('ENOSPC')) freeDiskSpace();
if (e.message.includes('EACCES')) fixPermissions();
} else throw e;
} Prevention
- Check free disk space before large batch downloads.
- Verify the output directory is writable (accessSync W_OK) before starting.
- Run one writer per output directory to avoid 'wx'-flag EEXIST races.
- Limit concurrency (p-limit) to avoid EMFILE from too many open files.
- Call cleanupNovelFile(plan) on failure so partial writes are removed.
When it happens
Trigger: fs.mkdirSync fails (permission denied on outputDir), openSync 'wx' fails (file appeared between prepare and commit — EEXIST; disk full — ENOSPC; too many open files — EMFILE), or writeFileSync fails with EIO mid-write.
Common situations: Disk quota exceeded or full disk when downloading large batches; another process creating the same destination between the exists-check and the write (TOCTOU with 'wx'); running out of file descriptors in large parallel batches; output folder made read-only mid-run.
Related errors
- Failed to inspect trace artifact ${dir}: ${err instanceof Er
- File could not be read: ${path}
- Receipt file cannot be read: ${receipt}
- Browser reported a completed download but the file is missin
- Could not store Midjourney ${kind} at ${filePath}: ${errorMe
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/2a804c1346b5e803.
Report an issue: GitHub.