Hmbown/CodeWhale · error · ExecError

coordinates must be finite numbers

Error message

coordinates must be finite numbers

What it means

clickAt validates its x/y coordinates with Number.isFinite before building the PowerShell mouse_event script. Non-finite coordinates (NaN, Infinity, undefined, non-numeric strings that coerce to NaN) throw this error immediately, before any input is injected. It is a pure input-validation guard protecting Windows' native input path.

Solutions

  1. Validate coordinates before clicking: ensure both are finite numbers (Number.isFinite(Number(x)) mirrors the check).
  2. Fix the coordinate source — check that the detection step actually produced a match before dispatching a click.
  3. Correct key names when destructuring (e.g. { x, y } vs { left, top }) so the right fields are passed.
  4. Clamp or reject out-of-range/scaled values before they reach the backend.

Example fix

// before
await backend.left_click({ target: { x: match?.cx, y: match?.cy } });
// after
const x = Number(match?.cx), y = Number(match?.cy);
if (!Number.isFinite(x) || !Number.isFinite(y)) throw new Error('no click target resolved');
await backend.left_click({ target: { x, y } });
Defensive patterns

Strategy: validation

Validate before calling

function assertClickablePoint(p) {
  const x = Number(p?.x), y = Number(p?.y);
  if (!Number.isFinite(x) || !Number.isFinite(y))
    throw new Error(`click target unresolved: ${JSON.stringify(p)}`);
  return { x, y };
}

Type guard

const isFinitePoint = (p) =>
  p != null && Number.isFinite(Number(p.x)) && Number.isFinite(Number(p.y));

Try / catch

try {
  await backend.left_click({ target: point });
} catch (e) {
  if (e.message === 'coordinates must be finite numbers') {
    console.warn('detection returned no point; skipping click', point);
    return null;
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling clickAt (directly or via left_click/double_click/right_click) with target.x or target.y that is undefined, null, NaN, Infinity, or a non-numeric string — typically from a malformed detection result or a JSON payload missing coordinates.

Common situations: An upstream vision/OCR step returned no match so x/y were never assigned; destructuring a click point with the wrong key names; screen-coordinate scaling math dividing by zero to produce Infinity.

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

Appendix: source

Thrown at crates/tui/plugins/computer-use/src/backends/win32.mjs:594

    recordingStart: async () => {
      throw Object.assign(new ExecError("Recording is unavailable on this platform until the recorder has session-owned cleanup. Use screenshots instead."), { code: "owned_recording_unavailable" });
    },
    recordingStop: async ({ id }) => { throw new ExecError(`unknown recording "${id}"`); },
    recordingStatus: ({ id }) => ({ id, running: false }),
    recordingList: async () => {
      const dir = recordingsDir();
      const out = fs.existsSync(dir)
        ? fs.readdirSync(dir).filter((f) => /\.mp4$/i.test(f)).map((f) => {
            const st = fs.statSync(path.join(dir, f));
            return { file: path.join(dir, f), bytes: st.size, modifiedAt: st.mtime.toISOString() };
          }).sort((a, b) => b.modifiedAt.localeCompare(a.modifiedAt)).slice(0, 50)
        : [];
      return { dir, recordings: out, running: [] };
    },
  };

  async function clickAt(button, x, y, clicks) {
    if (!Number.isFinite(Number(x)) || !Number.isFinite(Number(y))) throw new ExecError("coordinates must be finite numbers");
    const flags = button === 1 ? "RIGHTDOWN, RIGHTUP" : button === 2 ? "MIDDLEDOWN, MIDDLEUP" : "LEFTDOWN, LEFTUP";
    const seq = [];
    for (let i = 0; i < clicks; i++) seq.push(`[User32]::mouse_event([User32]::${flags.split(",")[0].trim()}, 0, 0, 0, [UIntPtr]::Zero); Start-Sleep -Milliseconds 40; [User32]::mouse_event([User32]::${flags.split(",")[1].trim()}, 0, 0, 0, [UIntPtr]::Zero); Start-Sleep -Milliseconds 60;`);
    const held = button === 1 ? "RIGHT" : button === 2 ? "MIDDLE" : "LEFT";
    throwIfAborted();
    heldButtons.add(held);
    try {
    await withUser32(`[User32]::SetCursorPos(${Math.round(x)}, ${Math.round(y)}) | Out-Null;
Start-Sleep -Milliseconds 60;
${seq.join("\n")}
Write-Output '{"ok": true}';`, { timeoutMs: 20_000 });
    heldButtons.delete(held);
    return { action_sent: true, at: { x: Number(x), y: Number(y) }, button, clicks };
    } finally { await releaseInput({ buttons: [held], keys: [] }); }
  }
}

export default { create };

View on GitHub (pinned to 73e0f67d83)