{"record":{"id":"d02eed5401b8075d","repo":"Hmbown/CodeWhale","slug":"target-outside-raster","errorCode":"target_outside_raster","errorMessage":"target (${x},${y}) is outside the bound raster (${r.pixels.w}x${r.pixels.h} pixels) — take a fresh screenshot","messagePattern":"target \\((.+?),(.+?)\\) is outside the bound raster \\((.+?)x(.+?) pixels\\) — take a fresh screenshot","errorType":"error_code","errorClass":"ServerError","httpStatus":null,"severity":"error","filePath":"crates/tui/plugins/computer-use/mcp/server.mjs","lineNumber":178,"sourceCode":"  const st = stateId ? appStates.get(stateId) : null;\n  if (!st) throw new ServerError(\"unknown_state\", target.state_id\n    ? `state_id \"${target.state_id}\" is unknown or expired — call get_app_state again`\n    : \"no observation on this computer yet — call get_app_state first\");\n  const el = st.elements[target.index];\n  if (!el) throw new ServerError(\"unknown_element\", `element index ${target.index} is outside state ${stateId} (0..${st.elements.length - 1})`);\n  return { state: st, element: el, stateId };\n}\n\nclass ServerError extends Error {\n  constructor(code, message, extra = null) { super(message); this.code = code; if (extra) this.extra = extra; }\n}\n\n/** Map raster-pixel coordinates to screen points using the bound raster. */\nfunction rasterToPoints(computerId, x, y) {\n  const r = lastRasters.get(computerId);\n  if (!r) throw new ServerError(\"no_raster\", \"no screenshot bound on this computer yet — call screenshot first so pixel targets have a frame\");\n  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      }","sourceCodeStart":160,"sourceCodeEnd":196,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/plugins/computer-use/mcp/server.mjs#L160-L196","documentation":"When a bound raster exists and records its pixel dimensions, rasterToPoints validates the requested pixel coordinates against them and throws code \"target_outside_raster\" if x or y is negative or beyond the captured width/height. This prevents silently clamping or wrapping coordinates into an unintended on-screen point; the message names the raster size and suggests re-screenshotting.","triggerScenarios":"Coordinate target in raster space where x < 0 || y < 0 || x >= pixels.w || y >= pixels.h: coordinates computed from a different-resolution screenshot, a scaled-up image the model read pixels from, or negative values from a bad subtraction.","commonSituations":"Display resolution changed between screenshot and action (window moved to another monitor, VM resized); using pixel coordinates from an image viewer that upscaled the screenshot; arithmetic on coordinates that underflows below zero.","solutions":["Take a fresh screenshot and recompute the pixel coordinates from the new raster.","Validate 0 <= x < width and 0 <= y < height against the screenshot's reported pixel size before sending.","If the display resolution changed, re-derive coordinates for the new geometry instead of rescaling old ones.","Use screen-space coordinates if your points come from a source independent of the raster."],"exampleFix":"// before\nawait click({ computerId, target: { type: \"coordinate\", space: \"raster\", x: 2400, y: 100 } }); // raster is 1920 wide\n\n// after\nconst shot = await screenshot(computerId);\nconst x = Math.min(2400 * (shot.pixels.w / 2560), shot.pixels.w - 1);\nawait click({ computerId, target: { type: \"coordinate\", space: \"raster\", x, y: 100 } });","handlingStrategy":"validation","validationCode":"const { w, h } = boundRaster(computerId).pixels;\nif (!(x >= 0 && x < w && y >= 0 && y < h)) throw new Error(`target (${x},${y}) outside raster ${w}x${h}`);","typeGuard":"const inRaster = (r, x, y) => r?.pixels?.w != null && r?.pixels?.h != null && x >= 0 && y >= 0 && x < r.pixels.w && y < r.pixels.h;","tryCatchPattern":"try {\n  return await click({ computerId, target: { type: \"coordinate\", space: \"raster\", x, y } });\n} catch (e) {\n  if (e.code === \"target_outside_raster\") {\n    const shot = await screenshot(computerId);\n    const { x: nx, y: ny } = recomputeFor(shot, x, y);\n    return await click({ computerId, target: { type: \"coordinate\", space: \"raster\", x: nx, y: ny } });\n  }\n  throw e;\n}","preventionTips":["Derive pixel coordinates from the same screenshot you bound, not an older one.","Clamp or bounds-check against the screenshot's reported width/height before sending.","Recompute coordinates after any display/VM resolution change.","Watch for subtraction-based math that can produce negatives."],"tags":["coordinates","bounds-check","screenshot","mcp"],"backgroundTag":"value-out-of-range","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}