{"record":{"id":"31010f92f0c42165","repo":"jackwener/OpenCLI","slug":"receipt-file-cannot-be-read-receipt","errorCode":null,"errorMessage":"Receipt file cannot be read: ${receipt}","messagePattern":"Receipt file cannot be read: (.+?)","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/mercury/utils.js","lineNumber":41,"sourceCode":"    const value = kwargs[name];\n    if (typeof value === 'boolean') return value;\n    if (typeof value === 'string') {\n        const normalized = value.trim().toLowerCase();\n        if (['1', 'true', 'yes', 'y', 'on'].includes(normalized)) return true;\n        if (['0', 'false', 'no', 'n', 'off'].includes(normalized)) return false;\n        throw new ArgumentError(`Boolean argument ${name} must be true or false`);\n    }\n    return fallback;\n}\n\nfunction parseReceiptPath(kwargs) {\n    const receipt = path.resolve(requireString(kwargs, 'receipt'));\n    let stat;\n    try {\n        stat = fs.statSync(receipt, { throwIfNoEntry: false });\n    }\n    catch (error) {\n        throw new ArgumentError(`Receipt file cannot be read: ${receipt}`, error instanceof Error ? error.message : undefined);\n    }\n    if (!stat || !stat.isFile()) {\n        throw new ArgumentError(`Receipt file does not exist: ${receipt}`);\n    }\n    return receipt;\n}\n\nfunction parseAmount(kwargs) {\n    const amount = requireString(kwargs, 'amount').replace(/,/g, '');\n    if (!/^\\d+(\\.\\d{1,2})?$/.test(amount) || Number(amount) <= 0) {\n        throw new ArgumentError(`Amount must be a positive number with up to two decimals: ${amount}`);\n    }\n    return amount;\n}\n\nfunction parseCurrency(kwargs) {\n    const currency = optionalString(kwargs, 'currency', 'CNY').toUpperCase();\n    if (!/^[A-Z]{3}$/.test(currency)) {","sourceCodeStart":23,"sourceCodeEnd":59,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/mercury/utils.js#L23-L59","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nnode mercury.js reimburse --receipt /mnt/usb/receipt.pdf   # unmounted drive\n// after\nnode mercury.js reimburse --receipt /home/alice/receipt.pdf  # local readable path","handlingStrategy":"validation","validationCode":"import fs from 'node:fs';\nfunction canStatReceipt(p) {\n  try { fs.accessSync(p, fs.constants.R_OK); return true; } catch { return false; }\n}","typeGuard":"function isReadableFile(p) {\n  try { return fs.statSync(p, { throwIfNoEntry: false })?.isFile() ?? false; } catch { return false; }\n}","tryCatchPattern":"try {\n  await reimburse({ receipt });\n} catch (e) {\n  if (e instanceof ArgumentError && e.message.startsWith('Receipt file cannot be read')) {\n    console.error(`Cannot access receipt (${e.message}). Check permissions/symlinks: ${e.cause ?? ''}`);\n    process.exitCode = 2;\n  } else throw e;\n}","preventionTips":["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"],"tags":["argument-validation","filesystem","permissions","input"],"backgroundTag":"file-not-accessible","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}