Hmbown/CodeWhale · error · ExecError
xdotool exited
Error message
xdotool ${args[0]} exited ${r.code}: ${r.stderr.trim().slice(0, 200)} What it means
Thrown by the xdotool wrapper when the xdotool subprocess exits non-zero on X11. The message includes the first xdotool subcommand (args[0], e.g. key, mousemove, click) plus up to 200 chars of stderr. The wrapper first verifies xdotool exists via `need`, so this error means xdotool ran but rejected the request.
Solutions
- Read the stderr in the message; xdotool errors usually name the bad keysym or 'Can't open display'.
- Ensure DISPLAY and XAUTHORITY are correct for the target X server (`xdotool getactivewindow` as a smoke test).
- Normalize modifier/key names before calling: this backend maps meta->logo only in waylandKey, so pass xdotool-valid names like 'ctrl', 'alt', 'super'.
- Re-resolve the target window id if you passed one; the window may have closed.
- If on Wayland, ensure the session probe picked the right backend (ydotool/wtype) rather than xdotool.
Example fix
// before await xdotool(['key', 'Meta_L']); // after await xdotool(['key', 'super']);
Defensive patterns
Strategy: try-catch
Validate before calling
async function xdotoolReady() {
try {
await run('xdotool', ['getdisplaygeometry'], { timeoutMs: 5000 });
return true;
} catch { return false; }
} Try / catch
try {
await xdotool(['key', name]);
} catch (e) {
if (e instanceof ExecError && e.message.startsWith('xdotool')) {
if (/Can't open display/.test(e.message)) fixDisplayEnv();
else if (/window.*invalid|Unknown window/i.test(e.message)) await refreshWindowTarget();
else console.error('xdotool rejected:', e.message);
} else throw e;
} Prevention
- Run `xdotool getdisplaygeometry` as a preflight in the target session.
- Use xdotool-valid key names (keysyms); map app shortcut names before passing.
- Keep DISPLAY/XAUTHORITY set for the invoking user.
- Re-resolve window ids just before interacting; windows close asynchronously.
- On Wayland, make sure the session probe routes input away from xdotool.
When it happens
Trigger: Any input action (key, type, click, move) routed through xdotool(args, opts) on X11 where xdotool exits non-zero: unknown keysym, no X display/authority, window target disappeared, or malformed coordinate arguments.
Common situations: SSH/headless X11 without DISPLAY set; X SECURITY blocking the connection; sending a key name xdotool doesn't recognize (e.g. 'meta' instead of mapping it); clicking a window that closed between lookup and action.
Related errors
- could not parse xdotool getmouselocation output
- wtype exited
- ydotool exited
- AT-SPI action failed
- Automation name is required
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/93e937071e35ad0e.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/plugins/computer-use/src/backends/linux.mjs:155
args.push(file);
} else {
if (region) args.push("-crop", `${Math.round(region[2])}x${Math.round(region[3])}+${Math.round(region[0])}+${Math.round(region[1])}`);
args.push(file);
}
const r = await run(cmd, args, { timeoutMs: 10_000 });
if (r.code !== 0) throw new ExecError(`${cmd} exited ${r.code}: ${r.stderr.trim().slice(0, 300)}`, r);
}
function recordingsDir() {
return path.resolve(process.env.CODEWHALE_CU_RECORDINGS_DIR || path.join(os.homedir(), ".codewhale-cu", "recordings"));
}
async function xdotool(args, opts = {}) {
need("xdotool", "input on X11");
throwIfAborted();
const r = await run("xdotool", args, opts);
throwIfAborted();
if (r.code !== 0) throw new ExecError(`xdotool ${args[0]} exited ${r.code}: ${r.stderr.trim().slice(0, 200)}`, r);
return r.stdout.trim();
}
async function ydotool(args, opts = {}) {
need("ydotool", "input on Wayland (ydotool needs its daemon running: sudo ydotoold)");
throwIfAborted();
const r = await run("ydotool", args, opts);
throwIfAborted();
if (r.code !== 0) throw new ExecError(`ydotool exited ${r.code}: ${r.stderr.trim().slice(0, 200)}`, r);
return r.stdout.trim();
}
function xdotoolKey(text) {
return String(text).split("+").map((p) => {
const k = p.trim().toLowerCase();
if (XKEYS[k]) return XKEYS[k];
if (/^f\d{1,2}$/.test(k)) return k.toUpperCase();
return p.trim(); // pass through names already in xdotool formView on GitHub (pinned to 73e0f67d83)