{"record":{"id":"321631d769cf4336","repo":"Hmbown/CodeWhale","slug":"bad-target","errorCode":"bad_target","errorMessage":"screen coordinates must be finite numbers","messagePattern":"screen coordinates must be finite numbers","errorType":"error_code","errorClass":"ServerError","httpStatus":null,"severity":"error","filePath":"crates/tui/plugins/computer-use/mcp/server.mjs","lineNumber":195,"sourceCode":"  if (r.pixels?.w != null && r.pixels?.h != null && (x < 0 || y < 0 || x >= r.pixels.w || y >= r.pixels.h)) {\n    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`);\n  }\n  const scale = r.scale && r.scale > 0 ? r.scale : 1;\n  return { x: (r.origin?.x ?? 0) + x / scale, y: (r.origin?.y ?? 0) + y / scale };\n}\n\n/**\n * Normalize a target into backend form: points for coordinates, resolved\n * element for elements. Element targets are revalidated against the live\n * backend when a resolver is available: stale elements throw `element_stale`,\n * moved-but-identical elements are re-aimed at their fresh center\n * (sink.reacquired = true so the receipt can say target_reacquired).\n */\nasync function normalizeTarget(computer, target, kind, resolve, sink) {\n  if (target?.type === \"coordinate\") {\n    if (target.space === \"screen\") {\n      if (!Number.isFinite(target.x) || !Number.isFinite(target.y)) {\n        throw new ServerError(\"bad_target\", \"screen coordinates must be finite numbers\");\n      }\n      return { x: Math.round(target.x), y: Math.round(target.y), strategy: \"event\", coordinate_space: \"screen\" };\n    }\n    if (target.x < 0 || target.y < 0) throw new ServerError(\"bad_target\", \"raster coordinates must be non-negative\");\n    const pt = rasterToPoints(computer.id, target.x, target.y);\n    return { x: Math.round(pt.x), y: Math.round(pt.y), strategy: \"event\", coordinate_space: \"raster\" };\n  }\n  if (target?.type === \"element\") {\n    const { state, element, stateId } = resolveElement(target, computer);\n    if (state.computerId && state.computerId !== computer.id) {\n      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`);\n    }\n    // The receipt must name the observation actually resolved — a bare index\n    // binds the computer's latest state, so reporting `target.state_id` would\n    // say \"undefined\" for the common case.\n    const where = `state ${stateId} (${state.app_ref?.name ?? state.app_ref?.bundle_id ?? `pid ${state.app_ref?.pid}`})`;\n    let fresh = null;\n    if (resolve) {","sourceCodeStart":177,"sourceCodeEnd":213,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/plugins/computer-use/mcp/server.mjs#L177-L213","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nawait move({ computerId, target: { type: \"coordinate\", space: \"screen\", x: \"512\", y: undefined } });\n\n// after\nconst x = Number(raw.x), y = Number(raw.y);\nif (!Number.isFinite(x) || !Number.isFinite(y)) throw new Error(`bad screen coords ${raw.x},${raw.y}`);\nawait move({ computerId, target: { type: \"coordinate\", space: \"screen\", x, y } });","handlingStrategy":"validation","validationCode":"const x = Number(raw.x), y = Number(raw.y);\nif (!Number.isFinite(x) || !Number.isFinite(y)) throw new Error(`screen coordinates must be finite numbers, got ${raw.x}, ${raw.y}`);","typeGuard":"const isFinitePoint = (p) => typeof p === \"object\" && p !== null && Number.isFinite(Number(p.x)) && Number.isFinite(Number(p.y));","tryCatchPattern":"try {\n  return await move({ computerId, target: { type: \"coordinate\", space: \"screen\", x, y } });\n} catch (e) {\n  if (e.code === \"bad_target\") {\n    const fixed = sanitizePoint(raw); // coerce strings, reject NaN/Infinity\n    if (!fixed) throw e;\n    return await move({ computerId, target: { type: \"coordinate\", space: \"screen\", ...fixed } });\n  }\n  throw e;\n}","preventionTips":["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."],"tags":["validation","coordinates","mcp","nan"],"backgroundTag":"invalid-argument-value","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-22T10:30:35.592Z"}