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

  1. Pass either app_ref/window_id or region, not both
  2. Capture the app window and crop to the region afterwards if both constraints are needed
  3. In generic wrappers, omit falsy/undefined optional fields before calling
  4. 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

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


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)