{"record":{"id":"91c9da6ef01d66b3","repo":"jackwener/OpenCLI","slug":"not-a-directory","errorCode":null,"errorMessage":"not a directory","messagePattern":"not a directory","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"clis/minimax/utils.js","lineNumber":186,"sourceCode":"}\n\nexport function resolveOutputDir(value) {\n    const raw = String(value ?? '').trim();\n    if (!raw) return path.join(os.homedir(), 'Music', 'minimax');\n    if (raw === '~') return os.homedir();\n    if (raw.startsWith('~/')) return path.join(os.homedir(), raw.slice(2));\n    if (raw.startsWith('~')) throw new ArgumentError(`Unsupported home-directory path: ${raw}`);\n    return path.resolve(raw);\n}\n\nexport function reserveAudioFile(dir, model, format, now = new Date()) {\n    const stamp = now.toISOString().replace(/[-:]/g, '').replace(/\\.\\d+Z$/, 'Z');\n    const target = path.join(dir, `${model}-${stamp}.${format}`);\n    const lock = `${target}.lock`;\n    const staging = `${target}.${process.pid}.${randomUUID()}.tmp`;\n    try {\n        fs.mkdirSync(dir, { recursive: true });\n        if (!fs.statSync(dir).isDirectory()) throw new Error('not a directory');\n        fs.accessSync(dir, fs.constants.W_OK);\n        if (fs.existsSync(target)) throw new Error('target already exists');\n        fs.writeFileSync(lock, String(process.pid), { flag: 'wx', mode: 0o600 });\n        if (fs.existsSync(target)) {\n            fs.rmSync(lock, { force: true });\n            throw new Error('target appeared during reservation');\n        }\n        return { target, lock, staging };\n    } catch (error) {\n        throw new CommandExecutionError(`MiniMax music cannot reserve output file ${target}: ${error?.message ?? error}`);\n    }\n}\n\nexport function commitAudioFile(reservation, bytes) {\n    try {\n        fs.writeFileSync(reservation.staging, bytes, { flag: 'wx', mode: 0o600 });\n        // A same-filesystem hard link publishes the complete staging inode and\n        // fails if target already exists on POSIX and Windows alike.","sourceCodeStart":168,"sourceCodeEnd":204,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/minimax/utils.js#L168-L204","documentation":"reserveAudioFile creates the output directory, then verifies it with statSync and asserts it is actually a directory before reserving a target file with a lock file. If the path exists but is not a directory (e.g. a regular file or symlink to a file), it throws Error('not a directory'), which the catch block re-wraps into CommandExecutionError('MiniMax music cannot reserve output file <target>: not a directory').","triggerScenarios":"Passing a dir argument that exists on disk as a regular file/symlink-to-file; or a path component where mkdirSync created something but statSync shows a non-directory inode.","commonSituations":"Config points the output dir at an existing file (e.g. a stale 'minimax' file left by a previous tool); a mount point replaced by a file; a broken symlink where a directory was expected.","solutions":["Inspect the path: run ls -la on the configured dir; if it is a file, remove or rename it (rm <path>) and re-run","Point the output-dir config at a genuine directory path","Check for symlinks: replace a symlink-to-file with a real directory (mkdir <path>)","Verify permissions/mount state if the path is on a network or removable volume"],"exampleFix":"// before: 'minimax' exists as a regular file\nreserveAudioFile('~/Music/minimax', model, 'wav'); // throws\n// after (shell)\n// rm ~/Music/minimax && mkdir -p ~/Music/minimax\nreserveAudioFile('~/Music/minimax', model, 'wav');","handlingStrategy":"validation","validationCode":"import * as fs from 'node:fs';\nfunction assertUsableDir(dir) {\n    try {\n        const st = fs.statSync(dir);\n        if (!st.isDirectory()) throw new Error(`${dir} exists but is not a directory`);\n    } catch (e) {\n        if (e.code === 'ENOENT') return; // will be created\n        if (String(e.message).includes('not a directory')) throw e;\n        throw e;\n    }\n}\n// run assertUsableDir(dir) before reserveAudioFile","typeGuard":"function isDirectoryPath(p) {\n    try { return fs.statSync(p).isDirectory(); } catch { return false; }\n}","tryCatchPattern":"try {\n    const r = reserveAudioFile(dir, model, format);\n} catch (e) {\n    if (String(e.message).includes('not a directory')) {\n        fs.rmSync(dir, { force: true });          // remove stray file\n        fs.mkdirSync(dir, { recursive: true });   // recreate as directory\n        // then retry reserveAudioFile\n    } else throw e;\n}","preventionTips":["ls -la the configured output dir once during setup to confirm it is a directory","Never point output-dir at a file path or a symlink to a file","Recreate stray files at that path as directories before running generation","Include a preflight stat check in automated pipelines"],"tags":["minimax","filesystem","directory","path"],"backgroundTag":"path-is-not-a-directory","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}