Hmbown/CodeWhale · error · ServerError
bad_target
bad_target
Error message
screen coordinates must be finite numbers
What it means
normalizeTarget validates screen-space coordinate targets and throws code "bad_target" when x or y is not a finite number (NaN, Infinity, or a non-numeric value). Screen points are rounded to integers and passed to OS event injection, so non-finite values would produce garbage or undefined behavior downstream.
Solutions
- Coerce and validate before sending: Number(x) then Number.isFinite check for both axes.
- Fix the upstream producer so coordinates are actual finite numbers, not strings or nulls.
- If coordinates are computed, guard the division/arithmetic that can yield NaN/Infinity.
- Use raster space (with a bound screenshot) if your values are pixel-based, ensuring they pass the same finite check.
Example fix
// before
await move({ computerId, target: { type: "coordinate", space: "screen", x: "512", y: undefined } });
// after
const x = Number(raw.x), y = Number(raw.y);
if (!Number.isFinite(x) || !Number.isFinite(y)) throw new Error(`bad screen coords ${raw.x},${raw.y}`);
await move({ computerId, target: { type: "coordinate", space: "screen", x, y } }); Defensive patterns
Strategy: validation
Validate before calling
const x = Number(raw.x), y = Number(raw.y);
if (!Number.isFinite(x) || !Number.isFinite(y)) throw new Error(`screen coordinates must be finite numbers, got ${raw.x}, ${raw.y}`); Type guard
const isFinitePoint = (p) => typeof p === "object" && p !== null && Number.isFinite(Number(p.x)) && Number.isFinite(Number(p.y));
Try / catch
try {
return await move({ computerId, target: { type: "coordinate", space: "screen", x, y } });
} catch (e) {
if (e.code === "bad_target") {
const fixed = sanitizePoint(raw); // coerce strings, reject NaN/Infinity
if (!fixed) throw e;
return await move({ computerId, target: { type: "coordinate", space: "screen", ...fixed } });
}
throw e;
} Prevention
- Coerce string coordinates with Number() and reject NaN before sending.
- Guard any division or arithmetic that can yield NaN/Infinity upstream.
- Require both axes explicitly; don't let optional fields default to undefined.
- Type-check LLM-emitted tool arguments before executing them.
When it happens
Trigger: target.type === "coordinate" with target.space === "screen" and Number.isFinite(target.x/y) false: null/undefined coordinates passed through, JSON strings like "500" not coerced, division by zero upstream producing NaN, or Infinity from an unbounded calculation.
Common situations: An LLM emits coordinates as strings or omits one axis; a computation like (a/b) yields NaN when b is 0; a caller forwards optional fields without defaults; serialization drops numeric types.
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
- coordinates must be finite numbers
- .annotations is not a valid MCP annotations object
- .inputSchema must be a valid object-shaped MCP input schema
- .size must be an integer
- coordinates must be finite numbers
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/321631d769cf4336.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/plugins/computer-use/mcp/server.mjs:195
if (r.pixels?.w != null && r.pixels?.h != null && (x < 0 || y < 0 || x >= r.pixels.w || y >= r.pixels.h)) {
throw new ServerError("target_outside_raster", `target (${x},${y}) is outside the bound raster (${r.pixels.w}x${r.pixels.h} pixels) — take a fresh screenshot`);
}
const scale = r.scale && r.scale > 0 ? r.scale : 1;
return { x: (r.origin?.x ?? 0) + x / scale, y: (r.origin?.y ?? 0) + y / scale };
}
/**
* Normalize a target into backend form: points for coordinates, resolved
* element for elements. Element targets are revalidated against the live
* backend when a resolver is available: stale elements throw `element_stale`,
* moved-but-identical elements are re-aimed at their fresh center
* (sink.reacquired = true so the receipt can say target_reacquired).
*/
async function normalizeTarget(computer, target, kind, resolve, sink) {
if (target?.type === "coordinate") {
if (target.space === "screen") {
if (!Number.isFinite(target.x) || !Number.isFinite(target.y)) {
throw new ServerError("bad_target", "screen coordinates must be finite numbers");
}
return { x: Math.round(target.x), y: Math.round(target.y), strategy: "event", coordinate_space: "screen" };
}
if (target.x < 0 || target.y < 0) throw new ServerError("bad_target", "raster coordinates must be non-negative");
const pt = rasterToPoints(computer.id, target.x, target.y);
return { x: Math.round(pt.x), y: Math.round(pt.y), strategy: "event", coordinate_space: "raster" };
}
if (target?.type === "element") {
const { state, element, stateId } = resolveElement(target, computer);
if (state.computerId && state.computerId !== computer.id) {
throw new ServerError("state_wrong_computer", `state_id "${stateId}" belongs to computer "${state.computerId}", not "${computer.id}" — call get_app_state on that computer again`);
}
// The receipt must name the observation actually resolved — a bare index
// binds the computer's latest state, so reporting `target.state_id` would
// say "undefined" for the common case.
const where = `state ${stateId} (${state.app_ref?.name ?? state.app_ref?.bundle_id ?? `pid ${state.app_ref?.pid}`})`;
let fresh = null;
if (resolve) {View on GitHub (pinned to 73e0f67d83)