jackwener/OpenCLI · error · ArgumentError
output must be a non-empty directory path
Error message
output must be a non-empty directory path
What it means
After type-checking, normalizePixivOutputRoot resolves the output value against the fallback and rejects values that are empty (after nullish coalescing with the fallback) or that contain a NUL byte ('\0'), which Node filesystem APIs reject. This guards against producing an unusable or malicious destination path.
Source
Thrown at clis/pixiv/novel-download-utils.js:64
export function normalizeNovelFileFormat(value) {
if (value !== undefined && typeof value !== 'string') {
throw new ArgumentError('Novel download format must be txt or md');
}
const format = (value ?? 'txt').toLowerCase();
if (format !== 'txt' && format !== 'md') {
throw new ArgumentError(`Unsupported novel download format: ${format}. Supported formats: txt, md.`);
}
return format;
}
export function normalizePixivOutputRoot(value, fallback) {
if (value !== undefined && typeof value !== 'string') {
throw new ArgumentError('output must be a directory path');
}
const raw = value ?? fallback;
if (!raw || raw.includes('\0')) {
throw new ArgumentError('output must be a non-empty directory path');
}
const resolved = path.resolve(raw);
let ancestor = resolved;
const missingParts = [];
let ancestorStat;
while (!ancestorStat) {
try {
ancestorStat = fs.lstatSync(ancestor);
} catch (error) {
if (error?.code !== 'ENOENT') {
throw new ArgumentError(`output path is not a safe directory: ${ancestor}`);
}
const parent = path.dirname(ancestor);
if (parent === ancestor) {
throw new ArgumentError(`output path is not a safe directory: ${resolved}`);
}
missingParts.unshift(path.basename(ancestor));
ancestor = parent;View on GitHub (pinned to 49907e53dc)
Solutions
- Provide a non-empty directory path string, e.g. './pixiv-downloads/novels'.
- If relying on the default, pass undefined (not '' or null with an empty fallback) so the fallback './pixiv-downloads/novels' applies.
- Strip or reject NUL bytes from user-supplied paths before calling the API.
Example fix
// before const dir = process.env.OUTPUT ?? ''; // '' bypasses fallback normalizePixivOutputRoot(dir); // after const dir = process.env.OUTPUT || undefined; normalizePixivOutputRoot(dir, './pixiv-downloads/novels');
Defensive patterns
Strategy: validation
Validate before calling
const dir = output ?? fallback;
if (typeof dir !== 'string' || dir.length === 0 || dir.includes('\0')) {
throw new TypeError('output must be a non-empty path string without NUL bytes');
} Type guard
function isUsablePath(v) {
return typeof v === 'string' && v.length > 0 && !v.includes('\0');
} Try / catch
try {
await downloadNovel(id, { output });
} catch (e) {
if (/output must be a non-empty directory path/.test(e.message)) {
console.error('Set a valid --output directory, e.g. ./pixiv-downloads/novels');
process.exitCode = 2;
} else throw e;
} Prevention
- Use || undefined instead of ?? '' for env-var paths so empty strings fall back to defaults.
- Sanitize user-supplied paths to strip control characters and NUL bytes.
- Give empty env/CLI overrides a sane default rather than an empty string.
When it happens
Trigger: Calling normalizePixivOutputRoot(''), normalizePixivOutputRoot(null, ''), or a path containing an embedded NUL character such as 'out\0dir'; also when both value and fallback are empty/undefined.
Common situations: Empty OUTPUT env var or shell option (-o "") that overrides the fallback; NUL bytes injected in a path from unsanitized user input or a corrupted config.
Related errors
- Unsupported home-directory path: ${raw}
- output must be a directory path
- <train-no> must not be empty
- <train-no> "${trainNo}" does not look like a 12306 internal
- --from station must not be empty
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/3b1df06532eb551e.
Report an issue: GitHub.