jackwener/OpenCLI · error · ArgumentError

Amount must be a positive number with up to two decimals: ${

Error message

Amount must be a positive number with up to two decimals: ${amount}

What it means

parseAmount strips commas and validates the amount string against ^\d+(\.\d{1,2})?$ and requires it be > 0. Anything with symbols, currency codes, thousands separators other than commas, exponents, negatives, or more than two decimals is rejected with this ArgumentError.

Source

Thrown at clis/mercury/utils.js:52

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)) {
        throw new ArgumentError(`Currency must be a three-letter ISO currency code: ${currency}`);
    }
    return currency;
}

function parseDate(kwargs) {
    const date = requireString(kwargs, 'date');
    const match = date.match(/^(\d{4})-(\d{2})-(\d{2})$/);
    if (!match) {
        throw new ArgumentError(`Date must be YYYY-MM-DD: ${date}`);
    }

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Supply the amount as a plain decimal string with '.' as the decimal separator and no currency symbols: e.g. '1234.56'.
  2. Remove thousands separators before passing (or use commas only as thousands separators, which the parser strips).
  3. Round/trim to at most two decimal places and ensure the value is greater than zero.
  4. If amounts come from another system, sanitize them first: strip symbols, convert ',' decimals to '.', then format with toFixed(2).

Example fix

// before
--amount "¥1,234.567"
// after
--amount "1234.57"
Defensive patterns

Strategy: validation

Validate before calling

function isValidAmount(a) {
  const s = String(a).replace(/,/g, '');
  return /^\d+(\.\d{1,2})?$/.test(s) && Number(s) > 0;
}

Try / catch

try {
  await reimburse({ amount });
} catch (e) {
  if (e instanceof ArgumentError && e.message.includes('Amount must be a positive number')) {
    console.error('Expected format: digits with optional .xx, e.g. 1234.56');
  } else throw e;
}

Prevention

When it happens

Trigger: amount values like '$12.50', '12,50' (European decimal comma, comma stripped leaving 1250), '-3', '0', '0.00', '12.345', '1e3', or empty string after comma removal.

Common situations: Copy-pasting amounts from invoices that include currency symbols; European number formatting using '.' as thousands separator ('1.234,56' -> '1234,56' fails); passing numbers with 3+ decimals from spreadsheets.

Related errors


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