jackwener/OpenCLI · error · ArgumentError

Argument "${argDef.name}" must be a valid number. Received:

Error message

Argument "${argDef.name}" must be a valid number. Received: "${val}"

What it means

When an argument definition declares type 'int' or 'number', coerceAndValidateArgs converts the supplied value with Number(). If the result is not finite (NaN/Infinity), it throws ArgumentError stating the value must be a valid number. Note 'int' additionally requires an integer (separate error).

Source

Thrown at src/execution.ts:71

export function coerceAndValidateArgs(cmdArgs: Arg[], kwargs: CommandArgs): CommandArgs {
  const result: CommandArgs = { ...kwargs };

  for (const argDef of cmdArgs) {
    const val = result[argDef.name];

    if (argDef.required && (val === undefined || val === null || val === '')) {
      throw new ArgumentError(
        `Argument "${argDef.name}" is required.`,
        argDef.help ?? `Provide a value for --${argDef.name}`,
      );
    }

    if (val !== undefined && val !== null) {
      if (argDef.type === 'int' || argDef.type === 'number') {
        const num = Number(val);
        if (!Number.isFinite(num)) {
          throw new ArgumentError(`Argument "${argDef.name}" must be a valid number. Received: "${val}"`);
        }
        if (argDef.type === 'int' && !Number.isInteger(num)) {
          throw new ArgumentError(`Argument "${argDef.name}" must be a valid integer. Received: "${val}"`);
        }
        result[argDef.name] = num;
      } else if (argDef.type === 'boolean' || argDef.type === 'bool') {
        if (typeof val === 'string') {
          const lower = val.toLowerCase();
          if (lower === 'true' || lower === '1') result[argDef.name] = true;
          else if (lower === 'false' || lower === '0') result[argDef.name] = false;
          else throw new ArgumentError(`Argument "${argDef.name}" must be a boolean (true/false). Received: "${val}"`);
        } else {
          result[argDef.name] = Boolean(val);
        }
      }

      const coercedVal = result[argDef.name];
      if (argDef.choices && argDef.choices.length > 0) {

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Pass a plain numeric value, e.g. --timeout 30.
  2. Strip units/separators from the value before passing it (parse 10s -> 10 in the script).
  3. For int-typed arguments, also ensure the value has no decimal part.
  4. Coerce in code first: Number(value) and check Number.isFinite before calling.

Example fix

// before
opencli run --timeout 30s
// after
opencli run --timeout 30
Defensive patterns

Strategy: validation

Validate before calling

function toNumber(v: unknown, label: string): number {
  const n = Number(v);
  if (!Number.isFinite(n)) throw new Error(`${label} must be a valid number, got: ${String(v)}`);
  return n;
}
kwargs.timeout = toNumber(kwargs.timeout, '--timeout');

Type guard

const isFiniteNumber = (v: unknown): v is number =>
  typeof v === 'number' && Number.isFinite(v);

Try / catch

try {
  await opencli.run(cmd, kwargs);
} catch (e) {
  if (/must be a valid number/.test(e.message)) {
    console.error(`${e.message} — strip units like 's'/'ms' and separators.`);
  } else throw e;
}

Prevention

When it happens

Trigger: Passing --retries abc, --timeout "10s", --count 1_000, or kwargs like { retries: 'fast' } to an argument typed number/int.

Common situations: Including units in the value (10s, 5ms); thousands separators or underscores; locale-formatted numbers (1,5); copying string config values into numeric flags.

Related errors


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