jackwener/OpenCLI · error · ArgumentError
Receipt file cannot be read: ${receipt}
Error message
Receipt file cannot be read: ${receipt} What it means
parseReceiptPath resolves the receipt argument to an absolute path and stats it before any work starts. If fs.statSync throws (e.g. permission denied, path on broken symlink loop, I/O error) the function wraps the failure in this ArgumentError and attaches the OS error message as the detail. It is distinct from the non-existence error, which is raised separately when stat returns no entry.
Source
Thrown at clis/mercury/utils.js:41
const value = kwargs[name];
if (typeof value === 'boolean') return value;
if (typeof value === 'string') {
const normalized = value.trim().toLowerCase();
if (['1', 'true', 'yes', 'y', 'on'].includes(normalized)) return true;
if (['0', 'false', 'no', 'n', 'off'].includes(normalized)) return false;
throw new ArgumentError(`Boolean argument ${name} must be true or false`);
}
return fallback;
}
function parseReceiptPath(kwargs) {
const receipt = path.resolve(requireString(kwargs, 'receipt'));
let stat;
try {
stat = fs.statSync(receipt, { throwIfNoEntry: false });
}
catch (error) {
throw new ArgumentError(`Receipt file cannot be read: ${receipt}`, error instanceof Error ? error.message : undefined);
}
if (!stat || !stat.isFile()) {
throw new ArgumentError(`Receipt file does not exist: ${receipt}`);
}
return receipt;
}
function parseAmount(kwargs) {
const amount = requireString(kwargs, 'amount').replace(/,/g, '');
if (!/^\d+(\.\d{1,2})?$/.test(amount) || Number(amount) <= 0) {
throw new ArgumentError(`Amount must be a positive number with up to two decimals: ${amount}`);
}
return amount;
}
function parseCurrency(kwargs) {
const currency = optionalString(kwargs, 'currency', 'CNY').toUpperCase();
if (!/^[A-Z]{3}$/.test(currency)) {View on GitHub (pinned to 49907e53dc)
Solutions
- Verify the file exists and is reachable: run ls -l on the exact path (after resolving symlinks) as the same user running the command.
- Fix permissions on the file and each parent directory (chmod/chown or move the file to an accessible location).
- Check the argument for stray whitespace, quotes, or a dangling symlink; re-supply a clean absolute path.
- Inspect the attached cause message (the error detail) — it contains the underlying OS reason (EACCES, ELOOP, etc.) to target the fix.
Example fix
// before node mercury.js reimburse --receipt /mnt/usb/receipt.pdf # unmounted drive // after node mercury.js reimburse --receipt /home/alice/receipt.pdf # local readable path
Defensive patterns
Strategy: validation
Validate before calling
import fs from 'node:fs';
function canStatReceipt(p) {
try { fs.accessSync(p, fs.constants.R_OK); return true; } catch { return false; }
} Type guard
function isReadableFile(p) {
try { return fs.statSync(p, { throwIfNoEntry: false })?.isFile() ?? false; } catch { return false; }
} Try / catch
try {
await reimburse({ receipt });
} catch (e) {
if (e instanceof ArgumentError && e.message.startsWith('Receipt file cannot be read')) {
console.error(`Cannot access receipt (${e.message}). Check permissions/symlinks: ${e.cause ?? ''}`);
process.exitCode = 2;
} else throw e;
} Prevention
- test -r "$RECEIPT" in shell wrappers before invoking
- Keep receipt files on local, mounted-while-running filesystems
- Resolve symlinks (realpath) before passing paths
- Avoid copying paths with embedded quotes/newlines from chat or PDFs
When it happens
Trigger: Calling normalizeReimbursementInput with a receipt path that points to a file the process cannot stat: parent directory not searchable, a dangling symlink (stat fails on some platforms), a path with invalid characters, or a device/disk error during stat.
Common situations: Running the CLI under a user without read access to ~/Downloads or a mounted share; receipt on an unmounted USB/network drive; typo'd path through a symlink pointing at a removed target; Windows paths with stray quotes from copy-paste.
Related errors
- Receipt file does not exist: ${receipt}
- File not found: ${path}
- File must be a readable text file: ${path}
- File could not be read: ${path}
- Video file not found: ${filePath}
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/31010f92f0c42165.
Report an issue: GitHub.