Hmbown/CodeWhale · error · ExecError
ydotool exited
Error message
ydotool exited ${r.code}: ${r.stderr.trim().slice(0, 200)} What it means
Thrown by the ydotool wrapper when the ydotool subprocess exits non-zero on Wayland. ydotool injects input via uinput, so a non-zero exit almost always means its daemon (ydotoold) is not running or the user lacks uinput access. Stderr (first 200 chars) is included in the message.
Solutions
- Start the daemon: `sudo ydotoold` (the need() hint in the source explicitly says ydotool requires it).
- Add your user to the input/uinput group and reload udev rules, then re-login.
- Verify manually: `ydotool key 30:1 30:0` — if it fails outside the library, fix the environment there.
- Check /dev/uinput exists (`sudo modprobe uinput`) and the socket path matches between daemon and client.
- Consider the wtype path for Wayland text/keys if your compositor supports the virtual-keyboard protocol.
Example fix
// before (shell, one-time setup missing) ydotool type 'hi' # exits non-zero: no daemon // after sudo ydotoold & ydotool type 'hi'
Defensive patterns
Strategy: try-catch
Validate before calling
import { existsSync } from 'fs';
function ydotoolReady() {
return existsSync('/dev/uinput') && process.getuid && daemonSocketReachable();
}
const daemonSocketReachable = () => { try { accessSync('/run/ydotoold/socket'); return true; } catch { return false; } }; Try / catch
try {
await ydotool(['key', code]);
} catch (e) {
if (e instanceof ExecError && e.message.includes('ydotool exited')) {
// daemon or uinput missing — surface install/daemon instructions to the user
throw new Error('ydotool needs ydotoold running and /dev/uinput access: sudo ydotoold');
}
throw e;
} Prevention
- Start ydotoold at boot (systemd unit) before using ydotool-based input.
- Add the invoking user to the input/uinput group with proper udev rules.
- Probe `ydotool` with a no-op gesture before real automations.
- Keep daemon and client versions matched (socket path changes across versions).
- Prefer wtype on compositors that support the virtual-keyboard protocol.
When it happens
Trigger: Input actions on Wayland routed through ydotool(args, opts) where the binary runs but fails: ydotoold daemon not running, /dev/uinput not writable by the user, or invalid arguments (e.g. unknown key code).
Common situations: Fresh Wayland setups where `sudo ydotoold` was never started; uinput kernel module or udev rules missing; running as a user not in the `input` group; ydotoold running as root while ydotool runs as another user with a mismatched socket.
Understand the failure class
Background: "not installed", "pip install", "required for": how missing-dependency errors surface across open-source libraries — this error's family across 34 libraries.
Related errors
- wtype exited
- xdotool exited
- AT-SPI action failed
- Automation name is required
- Automation prompt is required
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/1200f8870cf3cc58.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/plugins/computer-use/src/backends/linux.mjs:164
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 form
}).join("+");
}
async function waylandKey(text, { repeat = 1, holdMs = 0 } = {}) {
need("wtype", "key presses on Wayland");
const parts = String(text).split("+").map((part) => part.trim().toLowerCase());
const aliases = { control: "ctrl", meta: "logo", cmd: "logo", super: "logo" };
const modifiers = new Set(["ctrl", "alt", "shift", "logo", "win", "altgr", "capslock"]);
const rawKey = parts.pop();View on GitHub (pinned to 73e0f67d83)