Hmbown/CodeWhale · critical · ExecError
screencapture exited
Error message
screencapture exited ${r.code}: ${r.stderr.trim().slice(0, 300)} What it means
The darwin backend invokes the macOS `screencapture` CLI (20s timeout). If it exits with a non-zero status, the backend throws an ExecError embedding the exit code and up to 300 chars of stderr. This is a wrapper over OS-level capture failure — permission, session, or resource issues — not a validation error.
Solutions
- Grant Screen Recording permission to the host app/terminal in System Settings > Privacy & Security > Screen Recording, then restart it.
- Run inside a logged-in GUI session (not SSH/headless) where WindowServer is available.
- Read the embedded stderr in the error message for the specific screencapture failure reason.
- Unlock the screen and ensure a display is attached before retrying.
Defensive patterns
Strategy: try-catch
Try / catch
try { await screenshot() } catch (e) {
if (String(e.message).startsWith("screencapture exited")) {
// log stderr from the message; usually TCC screen-recording permission
console.error("Screen Recording permission or GUI session missing:", e.message);
} else throw e;
} Prevention
- Grant Screen Recording permission to the host app on first run (TCC) and restart it.
- Run only inside a logged-in GUI session, not SSH/headless/CI without WindowServer.
- Keep the 20s timeout in mind; don't issue overlapping captures.
When it happens
Trigger: Running without Screen Recording TCC permission (System Settings > Privacy & Security > Screen Recording); running in an SSH/headless session without a WindowServer; screencapture failing on an invalid -R rect or display; the screen-locked/secure-input state blocking capture.
Common situations: First run of the app after install where the permission prompt was dismissed; CI runners without a GUI login; kiosk/VNC environments where the window server is unavailable.
Related errors
- background preview capture failed
- open failed
- shared_pointer_required
- agent action=claim widens an enforced write scope, and the…
- agent profile may not disable approval_required
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/0b7e4967be95630b.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/plugins/computer-use/src/backends/darwin.mjs:548
// 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 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(),
};View on GitHub (pinned to 73e0f67d83)