Hmbown/CodeWhale · error · ExecError
region must be [x, y, w, h] in last-raster pixels
Error message
region must be [x, y, w, h] in last-raster pixels
What it means
zoom validates its `region` as exactly 4 non-negative integers in PIXELS of the last raster (not screen points), requiring w>0, h>0, and x+w / y+h within the raster's pixel bounds. Non-integer, empty, negative, or out-of-bounds regions all throw this error.
Solutions
- Use the `pixels.w`/`pixels.h` values returned by the previous screenshot to clamp the region: x+w <= pixels.w and y+h <= pixels.h.
- Convert screen points to raster pixels by multiplying by the screenshot's reported scale when necessary.
- Round all four values with Math.round/trunc to integers and ensure w,h > 0.
- Take a fresh screenshot if you suspect the raster was resized since you computed coordinates.
Example fix
// before
await zoom({ region: [10.5, 0, 800, 600] }); // points on a 1x-scaled 1200x800 raster
// after
await zoom({ region: [10, 0, 700, 500] }); // integers within pixels.w/h Defensive patterns
Strategy: validation
Validate before calling
const shot = await screenshot();
const [x, y, w, h] = region.map(Math.round);
if (w <= 0 || h <= 0 || x < 0 || y < 0 || x + w > shot.pixels.w || y + h > shot.pixels.h)
throw new Error(`region must fit within ${shot.pixels.w}x${shot.pixels.h} pixels`); Type guard
const isPixelRegion = (r, raster) => Array.isArray(r) && r.length === 4 && r.every(n => Number.isInteger(n) && n >= 0) && r[2] > 0 && r[3] > 0 && r[0] + r[2] <= raster.pixels.w && r[1] + r[3] <= raster.pixels.h;
Try / catch
try { await zoom({ region }) } catch (e) {
if (String(e.message).includes("last-raster pixels")) {
const { pixels } = await screenshot();
const [x, y, w, h] = region.map(n => Math.max(0, Math.round(n)));
await zoom({ region: [x, y, Math.min(w, pixels.w - x), Math.min(h, pixels.h - y)] });
} else throw e;
} Prevention
- Compute zoom regions in raster pixels, not screen points — check the screenshot's reported scale.
- Clamp against pixels.w/pixels.h from the latest screenshot; the raster may have been downscaled to fit a budget.
- Use integers only; round or truncate before calling.
When it happens
Trigger: Passing screen-point coordinates (from mouse position or window rects) instead of pixel coordinates; region extends past the screenshot's pixel width/height (e.g. after fitRasterToBudget downscaled the capture); fractional/float coordinates; w or h equal to 0.
Common situations: Retina displays where points != pixels (2x scale) causing out-of-bounds regions; the screenshot was downscaled to fit a raster budget so old coordinates no longer fit; agent emits floats like 12.5.
Understand the failure class
Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.
Related errors
- durationSec must be positive
- element has no resolved accessibility identity
- no screenshot taken yet on this computer — call screenshot…
- open_application needs name, bundle_id or pid
- region must be [x, y, w, h] in screen points
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/4239dd3ee9d46f96.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/plugins/computer-use/src/backends/darwin.mjs:574
...(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 from
// pixels/points below so Retina and mixed-DPI stay exact.
points: window?.points ?? (region ? { x: region[0], y: region[1], w: region[2], h: region[3] } : d?.points ?? null),
pixels: imagePixels(file),
scale: d?.scale ?? 1,
capturedAt: new Date().toISOString(),
};
if (state.lastRaster.points?.w) state.lastRaster.scale = state.lastRaster.pixels.w / state.lastRaster.points.w;
return { ...state.lastRaster, path: file };
}
async function zoom({ source, region, path: outPath }) {
if (!source && !state.lastRaster) throw new ExecError("no screenshot taken yet on this computer — call screenshot first");
const [x, y, w, h] = region;
if (![x, y, w, h].every((n) => Number.isInteger(n) && n >= 0) || !w || !h || x + w > state.lastRaster.pixels.w || y + h > state.lastRaster.pixels.h) throw new ExecError("region must be [x, y, w, h] in last-raster pixels");
const src = source ?? state.lastRaster.file;
const dir = recordingsDir();
fs.mkdirSync(dir, { recursive: true });
const out = outPath || path.join(dir, `zoom-${crypto.randomBytes(4).toString("hex")}.png`);
await runOk("sips", ["-s", "format", "png", "-c", String(Math.round(h)), String(Math.round(w)), "--cropOffset", String(Math.round(y)), String(Math.round(x)), src, "--out", out], { timeoutMs: 15_000 });
const parent = state.lastRaster;
state.lastRaster = { file: out, bytes: fs.statSync(out).size, source: src, region,
points: { x: (parent.points?.x ?? 0) + x / parent.scale, y: (parent.points?.y ?? 0) + y / parent.scale, w: w / parent.scale, h: h / parent.scale },
pixels: { w, h }, scale: parent.scale, capturedAt: new Date().toISOString() };
return state.lastRaster;
}
// ---------- recording ----------
const rec = new Map(); // Includes starting children so session close owns them too.
function requestRecordingStop(r) {
if (r.child.exitCode == null && r.child.signalCode == null) {
r.child.stdin.end();View on GitHub (pinned to 73e0f67d83)