Hmbown/CodeWhale · error · ExecError

must be a finite number

Error message

${name} must be a finite number

What it means

A synchronous argument validation helper: assertNum(v, name) coerces via Number() and throws when the result is not finite (NaN, Infinity, or non-numeric strings). It exists so numeric parameters like coordinates, sizes, and repeat counts fail fast with a message naming the offending parameter.

Solutions

  1. Coerce and validate inputs before calling the action: Number(value) and Number.isFinite checks at your boundary.
  2. Ensure required numeric fields are present in the action payload (missing fields become NaN here).
  3. Strip units from strings ('100px' -> 100) before passing.
  4. For computed values, guard divisions by zero that yield Infinity.
  5. Read the message's parameter name — it tells you exactly which argument was bad.

Example fix

// before
await move({ x: req.x, y: req.y }); // x may be '120px'
// after
const x = Number.parseFloat(req.x), y = Number(req.y);
if (!Number.isFinite(x) || !Number.isFinite(y)) throw new Error('bad coords');
await move({ x, y });
Defensive patterns

Strategy: validation

Validate before calling

const assertFinite = (v, name) => {
  const n = Number(v);
  if (!Number.isFinite(n)) throw new TypeError(`${name} must be a finite number, got ${String(v)}`);
  return n;
};
// call before invoking any action: assertFinite(payload.x, 'x'), etc.

Type guard

const isFiniteNumber = (v) => typeof v === 'number' && Number.isFinite(v);
// or for numeric strings: const isCoercible = (v) => Number.isFinite(Number(v));

Try / catch

try {
  await clickAt({ x: payload.x, y: payload.y });
} catch (e) {
  if (/must be a finite number/.test(e.message)) {
    const name = e.message.split(' ')[0];
    console.error(`Fix payload field '${name}': must be numeric, got`, payload[name.trim()]);
  } else throw e;
}

Prevention

When it happens

Trigger: Passing a non-numeric or non-finite value where a number is required: undefined coordinates from a malformed action payload, strings like '100px', null, NaN from upstream math, or a region element that is not a number.

Common situations: Tool-call JSON with coordinates as strings ('x': '120'); deriving a coordinate from a division that produced Infinity; forgetting a required field so undefined reaches Number(undefined) === NaN; region arrays built from regex matches that were not parseFloat'ed.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/2310e36f0f7b1239. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/plugins/computer-use/src/backends/linux.mjs:206

    const args = mods.flatMap((mod) => ["-M", mod]);
    for (let i = 0; i < repeat; i++) {
      args.push(onlyModifier ? "-M" : "-P", onlyModifier ? modKey : key);
      if (holdMs) args.push("-s", String(holdMs));
      args.push(onlyModifier ? "-m" : "-p", onlyModifier ? modKey : key);
    }
    args.push(...mods.reverse().flatMap((mod) => ["-m", mod]));
    // wtype owns a temporary Wayland keyboard; the compositor releases its
    // keys on process exit, including cancellation. Keep the complete gesture
    // in one process (https://github.com/atx/wtype#usage).
    throwIfAborted();
    const result = await run("wtype", args, { timeoutMs: Math.max(10_000, holdMs + 8_000) });
    throwIfAborted();
    if (result.code !== 0) throw new ExecError(`wtype exited ${result.code}: ${result.stderr.trim().slice(0, 200)}`, result);
  }

  function assertNum(v, name) {
    const n = Number(v);
    if (!Number.isFinite(n)) throw new ExecError(`${name} must be a finite number`);
    return n;
  }

  // ---------- AT-SPI tree ----------
  const PYATSPI_APP = `def resolve_app(desktop, app_name):
    apps = [desktop.getChildAtIndex(i) for i in range(desktop.childCount)]
    if app_name:
        matches = [app for app in apps if app and app_name.casefold() == (app.name or "").casefold()]
        return matches[0] if len(matches) == 1 else None
    return next((app for app in apps if app and app.childCount), None)
`;
  const PYATSPI_WALK = `import json, sys, pyatspi
${PYATSPI_APP}
app_name = sys.argv[1] if len(sys.argv) > 1 else None
depth_max = int(sys.argv[2]) if len(sys.argv) > 2 else 8
max_el = int(sys.argv[3]) if len(sys.argv) > 3 else 400
desktop = pyatspi.Registry.getDesktop(0)
root = resolve_app(desktop, app_name)

View on GitHub (pinned to 73e0f67d83)