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

  1. Start the daemon: `sudo ydotoold` (the need() hint in the source explicitly says ydotool requires it).
  2. Add your user to the input/uinput group and reload udev rules, then re-login.
  3. Verify manually: `ydotool key 30:1 30:0` — if it fails outside the library, fix the environment there.
  4. Check /dev/uinput exists (`sudo modprobe uinput`) and the socket path matches between daemon and client.
  5. 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

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


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)