jackwener/OpenCLI · error · ArgumentError

File must be a readable text file: ${path}

Error message

File must be a readable text file: ${path}

What it means

After stat() succeeds, readUtf8File checks fileStat.isFile() to ensure the target is a regular file, not a directory, FIFO, socket, or device. If it is not a regular file it throws ArgumentError 'File must be a readable text file: <path>'.

Source

Thrown at clis/_atlassian/shared.js:301

    return n;
}

export function requireExecute(args, commandName) {
    if (args.execute !== true) {
        throw new ArgumentError(`${commandName} requires --execute to perform a remote write`);
    }
}

export async function readUtf8File(filePath) {
    const path = requireString(filePath, '--file');
    let fileStat;
    try {
        fileStat = await stat(path);
    } catch {
        throw new ArgumentError(`File not found: ${path}`);
    }
    if (!fileStat.isFile()) {
        throw new ArgumentError(`File must be a readable text file: ${path}`);
    }
    let raw;
    try {
        raw = await readFile(path);
    } catch {
        throw new ArgumentError(`File could not be read: ${path}`);
    }
    try {
        return new TextDecoder('utf-8', { fatal: true }).decode(raw);
    } catch {
        throw new ArgumentError(`File could not be decoded as UTF-8 text: ${path}`);
    }
}

export function htmlEscape(value) {
    return String(value ?? '')
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Pass the path to a regular file, not a directory or special file.
  2. Check the target with `file <path>` or `stat -c %F <path>` to confirm it is a regular file.
  3. If you meant to send several files, iterate over the directory and pass each file individually.

Example fix

// before
cli confluence create --file ./pages/
// after
cli confluence create --file ./pages/index.md
Defensive patterns

Strategy: validation

Validate before calling

const s = await stat(p);
if (!s.isFile()) throw new Error(`--file must be a regular file, got: ${p}`);

Type guard

const isRegularFile = async (p) => { try { return (await stat(p)).isFile(); } catch { return false; } };

Try / catch

try {
  const text = await readUtf8File(p);
} catch (err) {
  if (err.name === 'ArgumentError' && err.message.includes('readable text file')) {
    console.error(`${p} is not a regular file; pass a file, not a directory or device.`);
  }
  throw err;
}

Prevention

When it happens

Trigger: Passing a directory path (e.g. --file ./docs) or a special file (named pipe, /dev/ node) to readUtf8File.

Common situations: Pointing --file at a folder instead of a file by mistake; shell glob or variable expansion resolving to a directory; passing a device or procfs path expecting it to behave like a text file.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


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