Hmbown/CodeWhale · error · ExecError

coordinates must be finite numbers

Error message

coordinates must be finite numbers

What it means

assertInScreen guards every pointer/coordinate action: x and y must both be finite numbers (screen coordinates). NaN, Infinity, or non-numeric coordinates would otherwise be forwarded to the native helper and produce undefined clicks at arbitrary positions, so they are rejected up front.

Solutions

  1. Parse coordinates with Number() and validate Number.isFinite(x) && Number.isFinite(y) before calling the tool.
  2. Take a fresh screenshot/observe to get real coordinates instead of reusing ones derived from stale or failed data.
  3. Fix scaling math: guard every multiplication/division that derives x/y from image-vs-screen scale factors.
  4. If arguments come from a model, tighten the tool schema so x/y are required finite numbers.

Example fix

// before
const x = annot.left * scale; // annot.left undefined → NaN
await click({ x, y });

// after
const x = Number(annot?.left) * scale, y = Number(annot?.top) * scale;
if (!Number.isFinite(x) || !Number.isFinite(y)) throw new Error("bad point");
await click({ x, y });
Defensive patterns

Strategy: type-guard

Validate before calling

function isPoint(p) { return Number.isFinite(p?.x) && Number.isFinite(p?.y); }
if (!isPoint(pt)) throw new Error('coordinates must be finite numbers before calling the tool');

Type guard

function isFinitePoint(p) {
  return typeof p === 'object' && p !== null && Number.isFinite(p.x) && Number.isFinite(p.y);
}

Try / catch

try {
  await click({ x, y });
} catch (e) {
  if (String(e.message).includes('coordinates must be finite')) {
    const shot = await screenshot(); // re-derive coordinates from fresh data
    ({ x, y } = toScreenPoint(shot));
    await click({ x, y });
  } else throw e;
}

Prevention

When it happens

Trigger: Calling a coordinate-based tool (click, move, scroll, drag endpoints) with x or y that is NaN, ±Infinity, undefined, null, or a non-number — usually from parsing screenshot annotations into bad numbers or dividing by zero when scaling coordinates.

Common situations: LLM-emitted arguments with missing/garbled coordinates; coordinate scaling math producing NaN (e.g. multiplying undefined); JSON payloads where coordinates arrive as strings like "120" that later math turns into NaN; empty screenshot annotations yielding undefined points.

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/83cc94236a41bbd3. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/plugins/computer-use/src/backends/darwin.mjs:361

    // Any successful capture (bind, explicit preview, action refresh) starts
    // the live refresh; the tick itself re-enters this function as a no-op.
    if (state.previewEnabled && state.inputApp) startPreviewLoop();
    return { enabled: true, file, app: state.inputApp, pointer: p };
  }

  // ---------- pointer input ----------
  // Our qualified raw pointer path uses the shared event tap, which moves
  // the user's real cursor. Process/window-directed mouse delivery has not
  // passed the independent fixture. So the pointer path is:
  //   1. accessibility action on the element under the point (quiet, exact),
  //   2. otherwise refuse in background mode. Explicit foreground control
  //      permits a global gesture only when the bound application owns the
  //      window under the point. Restoring the cursor is not isolation.
  // Every receipt says which of the two happened.
  function mouseName(button) { return { left: "left", right: "right", middle: "middle" }[button] ?? "left"; }

  function assertInScreen(x, y) {
    if (!Number.isFinite(x) || !Number.isFinite(y)) throw new ExecError("coordinates must be finite numbers");
  }

  function buttonCode(button) { return button === "middle" ? 2 : button === "right" ? 1 : 0; }

  function requireSharedPointer() {
    if (!state.foregroundInput) throw Object.assign(new ExecError("This action needs the shared macOS pointer and was not sent in background mode. Use an accessibility action or a separate computer; foreground control requires exclusive desktop use authorized by the user."), { code: "shared_pointer_required" });
  }

  /** Refuse a global gesture whose landing point belongs to another application. */
  async function assertOwnsPoint(x, y) {
    if (!state.inputApp) throw new ExecError("open_application first to choose which application receives input");
    const w = await native("window_at_point", { x, y });
    if (!w?.found) throw new ExecError(`no window at (${x}, ${y}) — take a fresh screenshot and choose a point inside the target window`);
    if (w.owner_pid !== state.inputApp.pid) {
      throw new ExecError(`(${x}, ${y}) is covered by a window owned by ${w.owner_name || "another application"} (pid ${w.owner_pid}) — use an accessibility element target or a separate computer; no pointer input was sent`);
    }
    return w;
  }

View on GitHub (pinned to 73e0f67d83)