Hmbown/CodeWhale · error · ExecError
choose app_ref or region, not both
Error message
choose app_ref or region, not both
What it means
A screenshot capture may target either a specific application window (app_ref/window_id) or a screen region — not both, since an explicit app reference resolves alone and must not be redirected by a region. Supplying both is rejected to keep the capture target unambiguous.
Solutions
- Pass either app_ref/window_id or region, not both
- Capture the app window and crop to the region afterwards if both constraints are needed
- In generic wrappers, omit falsy/undefined optional fields before calling
- Use display + region for screen-area captures, or app_ref alone for window captures
Example fix
// before
await screenshot({ appRef: app.pid, region: { x: 0, y: 0, w: 100, h: 100 } });
// after
await screenshot({ appRef: app.pid }); Defensive patterns
Strategy: validation
Validate before calling
if (appRef !== undefined && region) throw new Error("appRef and region are mutually exclusive"); Try / catch
try {
await screenshot(opts);
} catch (e) {
if (e instanceof ExecError && e.message.includes("choose app_ref or region")) {
// drop region and retry window-scoped, or drop appRef and retry region-scoped
} else throw e;
} Prevention
- In generic wrappers, strip undefined/empty optional fields before forwarding
- Model the two capture modes as separate tool operations so they cannot collide
- Crop window captures as post-processing instead of passing region alongside app_ref
When it happens
Trigger: Calling screenshot with both app_ref and region set, e.g. copying parameters from two different call paths into one invocation, or a generic wrapper that forwards all optional fields unconditionally.
Common situations: Tool wrappers spreading user options without filtering null/undefined; scripts that combine 'capture this app' with 'crop to region' expectations — cropping must be done as a post-processing step.
Related errors
- display index must be a positive integer
- region must be integer [x,y,width,height] with positive size
- region must be [x, y, w, h] in screen points
- screenshot path must end in .png, .jpg or .jpeg
- 1
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/02fc87b6a8e568e1.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/plugins/computer-use/src/backends/darwin.mjs:525
async function screenshot({ display, region, app_ref, window_id, path: outPath } = {}) {
// Once an app is selected, ordinary observations follow it behind the
// user's work. An explicit display/region remains a deliberate desktop capture.
if (app_ref === undefined && display === undefined && region === undefined) app_ref = state.inputApp ?? undefined;
const dir = recordingsDir();
fs.mkdirSync(dir, { recursive: true });
// JPEG, not PNG. A screen is photographic content — gradients, wallpaper,
// antialiased text — and lossless compression of it is enormous: the same
// 5760x3240 frame is 21.8MB as PNG and 2.1MB as JPEG, at full resolution
// and with terminal text still crisp. PNG stays available by asking for a
// `.png` path, which is what a pixel-exact comparison wants.
const file = outPath || path.join(dir, `shot-${new Date().toISOString().replace(/[:.]/g, "-")}-${crypto.randomBytes(3).toString("hex")}.jpg`);
if (!/\.(png|jpe?g)$/i.test(file)) throw new ExecError("screenshot path must end in .png, .jpg or .jpeg");
const args = ["-x", "-t", /\.png$/i.test(file) ? "png" : "jpg"];
const disp = display ?? state.activeDisplay;
// An explicit app reference resolves first and alone: nothing may run
// before it and redirect the capture to another target.
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");
}View on GitHub (pinned to 73e0f67d83)