jackwener/OpenCLI · error · ArgumentError

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

Error message

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

What it means

For arguments typed 'int', coerceAndValidateArgs requires the numeric value to be a whole number. After the finite-number check passes, a non-integer value (e.g. 2.5) throws ArgumentError stating the argument must be a valid integer.

Source

Thrown at src/execution.ts:74

  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) {
        if (!argDef.choices.map(String).includes(String(coercedVal))) {
          throw new ArgumentError(`Argument "${argDef.name}" must be one of: ${argDef.choices.join(', ')}. Received: "${coercedVal}"`);
        }

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Pass a whole number, e.g. --workers 3.
  2. Round/ceil the computed value in the calling script (Math.ceil(x)) before passing.
  3. If fractional values are legitimate, check whether the argument should be typed 'number' instead of 'int' (library-side change).

Example fix

// before
opencli run --workers $((total / 2))   # 2.5
// after
opencli run --workers $(( (total + 1) / 2 ))   # integer
Defensive patterns

Strategy: validation

Validate before calling

function toInt(v: unknown, label: string): number {
  const n = Number(v);
  if (!Number.isFinite(n) || !Number.isInteger(n)) throw new Error(`${label} must be an integer, got: ${String(v)}`);
  return n;
}
kwargs.workers = toInt(kwargs.workers, '--workers');

Type guard

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

Try / catch

try {
  await opencli.run(cmd, kwargs);
} catch (e) {
  if (/must be a valid integer/.test(e.message)) {
    console.error(`${e.message} — round the value before passing (Math.ceil/Math.floor).`);
  } else throw e;
}

Prevention

When it happens

Trigger: Passing --workers 2.5, --retries 1.0 with a decimal representation via kwargs (number 1.5), or any fractional value to an int-typed flag.

Common situations: Dividing a total by a count in scripts (total/2) producing fractions; percentages passed as 0.5 where an integer count is expected; defaults copied from float-based configs.

Related errors


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