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
- Coerce and validate inputs before calling the action: Number(value) and Number.isFinite checks at your boundary.
- Ensure required numeric fields are present in the action payload (missing fields become NaN here).
- Strip units from strings ('100px' -> 100) before passing.
- For computed values, guard divisions by zero that yield Infinity.
- 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
- Validate action payloads with a schema (zod/ajv) that enforces finite numbers.
- Parse numeric strings and strip units at your boundary.
- Guard computed coordinates against division by zero / NaN propagation.
- Treat missing required fields as errors before dispatch (undefined -> NaN here).
- Log the raw payload when this fires — it usually indicates upstream serialization drift.
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
- --attempts must be an integer from 1 to 20
- --channel, --version and --reason are required
- Choose one Runtime --thread ID.
- click supports left with 1-3 clicks, right x1 or middle x1
- Command is required
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)