Hmbown/CodeWhale · error · ExecError
no display ; have [ ] — screenshot takes the display index…
Error message
no display ${disp}; have [${displays.map((x) => x.index).join(", ")}] — screenshot takes the display index from list_displays, not its id What it means
The darwin computer-use screenshot action validates the requested display against the index field of entries returned by list_displays/displayInfo. Callers that pass a display `id` (a macOS CGDisplay UUID) instead of the numeric `index` fail this check. The library throws early so a wrong display value cannot silently produce points/scale that mis-target all later coordinate actions.
Solutions
- Re-run list_displays and pass the `index` field (not `id`) as the screenshot display argument.
- If you don't need a specific display, omit `display` entirely or pass "all" to capture everything.
- After a monitor connect/disconnect, refresh the display list before retrying.
- Note that window captures ignore `display` entirely — use window_id instead if you meant to capture a window.
Example fix
// before
await screenshot({ display: displays[0].id });
// after
await screenshot({ display: displays[0].index }); Defensive patterns
Strategy: validation
Validate before calling
const displays = await list_displays();
if (display != null && display !== "all" && !displays.some(d => d.index === display))
throw new Error(`use display index from list_displays, not id: ${display}`); Type guard
const isDisplayIndex = (v, displays) => v === "all" || displays.some(d => d.index === v);
Try / catch
try { await screenshot({ display }) } catch (e) {
if (String(e.message).startsWith("no display")) {
const displays = await list_displays();
await screenshot({ display: displays[0].index });
} else throw e;
} Prevention
- Always take the screenshot display argument from list_displays `index`, never `id`.
- Refresh the display list after any monitor connect/disconnect.
- Use window_id for window captures — display is ignored there anyway.
When it happens
Trigger: Calling screenshot({ display: <display id> }) where the value came from the `id` field of list_displays output rather than its numeric `index`; also triggered by a stale index after displays were plugged/unplugged, or by any non-'all' value not found in displays[].index.
Common situations: Copy-pasting a display id from list_displays JSON into the screenshot call; an LLM agent confusing `id` with `index`; hardware display changes making a previously valid index disappear; passing a 0-based index to a 1-based list.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- region must be [x, y, w, h] in screen points
- application not found — call list_apps for exact names/pids
- attempt finalization requires a terminal worker event
- background preview capture failed
- cannot install a bundle from inside the user plugins…
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/7a20d38f407d5983.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/plugins/computer-use/src/backends/darwin.mjs:535
// `.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");
}
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;View on GitHub (pinned to 73e0f67d83)