Hmbown/CodeWhale · error · ExecError
region must be [x, y, w, h] in screen points
Error message
region must be [x, y, w, h] in screen points
What it means
The screenshot action validates the optional `region` option: it must be an array of exactly 4 finite, non-negative numbers representing [x, y, w, h] in screen points. The library rejects anything else before shelling out to macOS `screencapture -R`, which expects a comma-separated rect.
Solutions
- Reshape the region to a strict 4-element numeric array [x, y, w, h] in screen points.
- Clamp negative x/y to 0 and coerce all values with Number() before calling.
- If you have an {x,y,width,height} object, map it to [x, y, width, height].
- Omit `region` if you want the whole display.
Example fix
// before
await screenshot({ region: { x: 10, y: 20, width: 300, height: 200 } });
// after
await screenshot({ region: [10, 20, 300, 200] }); Defensive patterns
Strategy: validation
Validate before calling
const ok = Array.isArray(region) && region.length === 4 && region.every(n => Number.isFinite(n) && n >= 0);
if (!ok) throw new Error("region must be [x, y, w, h] in screen points"); Type guard
const isRegion = (r) => Array.isArray(r) && r.length === 4 && r.every(n => Number.isFinite(n) && n >= 0);
Try / catch
try { await screenshot({ region }) } catch (e) {
if (String(e.message).includes("region must be")) {
const [x, y, w, h] = region && typeof region === "object" && !Array.isArray(region)
? [region.x, region.y, region.width, region.height] : region;
await screenshot({ region: [Math.max(0, Math.round(x)), Math.max(0, Math.round(y)), Math.round(w), Math.round(h)] });
} else throw e;
} Prevention
- Normalize {x,y,width,height} objects to 4-arrays before every screenshot call.
- Coerce JSON-round-tripped values with Number() to avoid string elements.
- Clamp origins to >= 0 when windows can sit partially off-screen.
When it happens
Trigger: Calling screenshot({ region: ... }) with: a region of length != 4; containing NaN/Infinity, negative values, or non-numeric entries (e.g. strings from JSON parsing); or null/undefined array elements.
Common situations: Agent emits region as strings ('100','200') or as {x,y,width,height} object instead of a 4-array; accidental negative origin when a window moved off-screen; JSON round-trip turning values into strings.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- choose app_ref or region, not both
- display index must be a positive integer
- durationSec must be positive
- element has no resolved accessibility identity
- no display ; have [ ] — screenshot takes the display index…
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/06d6f7e12b7fcd97.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/plugins/computer-use/src/backends/darwin.mjs:542
const window = app_ref !== undefined ? await native("window_info", { app_ref, window_id }) : null;
if (window && region) throw new ExecError("choose app_ref or region, not both");
// On the display path, resolve displays before capturing so an unknown
// index is a clean error instead of a raster silently labelled with another
// display's geometry — list_displays reports `index` and `id` separately,
// and a caller passing the id would otherwise get points and scale that
// mis-target every later coordinate. A window capture ignores `display`.
let displays = null;
if (!window) {
displays = await displayInfo();
if (disp != null && disp !== "all" && !displays.some((x) => x.index === disp)) {
throw new ExecError(`no display ${disp}; have [${displays.map((x) => x.index).join(", ")}] — screenshot takes the display index from list_displays, not its id`);
}
}
if (window) args.push("-o", "-l", String(window.window_id));
else if (disp && disp !== "all") args.push("-D", String(disp));
if (region) {
if (!region.every((n) => Number.isFinite(n) && n >= 0) || region.length !== 4) {
throw new ExecError("region must be [x, y, w, h] in screen points");
}
args.push("-R", region.join(","));
}
args.push(file);
const r = await runL("screencapture", args, { timeoutMs: 20_000 });
if (r.code !== 0) throw new ExecError(`screencapture exited ${r.code}: ${r.stderr.trim().slice(0, 300)}`, r);
await fitRasterToBudget(file);
const stat = fs.statSync(file);
displays ??= await displayInfo();
const d = displays.find((x) => x.index === (disp === "all" ? 1 : disp)) ?? displays[0];
const scale = d?.scale ?? 1;
state.lastRaster = {
file,
...(window ? { app_ref, window_index: window_id ?? 0 } : {}),
bytes: stat.size,
display: disp ?? 1,
// Region and window rasters describe that rect, not the whole display.
// The PNG header is the pixel ground truth; scale is derived fromView on GitHub (pinned to 73e0f67d83)