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

  1. Coerce and validate before sending: Number(x) then Number.isFinite check for both axes.
  2. Fix the upstream producer so coordinates are actual finite numbers, not strings or nulls.
  3. If coordinates are computed, guard the division/arithmetic that can yield NaN/Infinity.
  4. 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

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


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)