Hmbown/CodeWhale · error · ExecError

wtype exited

Error message

wtype exited ${result.code}: ${result.stderr.trim().slice(0, 200)}

What it means

Thrown by the Wayland key path when the wtype subprocess exits non-zero. wtype synthesizes a temporary virtual Wayland keyboard and the code keeps the whole gesture (press, hold, release) in one process; the error surfaces wtype's own failure with its stderr (first 200 chars). Abort is checked before and after the run, so this is purely a wtype-level failure.

Solutions

  1. Read wtype's stderr in the message; 'compositor does not support' means you must switch input backends (ydotool + ydotoold).
  2. Keep holdMs modest or raise the timeout path; the runtime is max(10000, holdMs+8000) ms.
  3. Test wtype directly: `wtype -P ctrl -p ctrl` to confirm compositor support.
  4. On GNOME, install ydotool and start ydotoold instead of relying on wtype.
  5. Ensure the session probe identified Wayland correctly; under XWayland-only apps wtype gestures may not land.

Example fix

// before
await waylandKey('ctrl+c', { holdMs: 60000 }); // exceeds timeout
// after
await waylandKey('ctrl+c', { holdMs: 500 });
Defensive patterns

Strategy: try-catch

Validate before calling

async function wtypeWorks() {
  try { await run('wtype', ['-P', 'shift', '-p', 'shift'], { timeoutMs: 5000 }); return true; }
  catch { return false; }
}
const holdOk = (holdMs) => !holdMs || holdMs <= 60_000;

Try / catch

try {
  await waylandKey(combo, { holdMs });
} catch (e) {
  if (e instanceof ExecError && e.message.includes('wtype exited')) {
    // fall back to ydotool (with ydotoold) — wtype unsupported on this compositor
    await ydotoolKey(combo);
  } else throw e;
}

Prevention

When it happens

Trigger: wtype present and selected on Wayland, but the compositor rejects the virtual keyboard protocol (e.g. GNOME does not support zwp_virtual_keyboard_v1 for wtype), the gesture exceeds the timeout (max(10s, holdMs+8s)), or invalid key args were passed through.

Common situations: Running wtype on GNOME/Wayland compositors lacking the virtual-keyboard protocol; extremely long holdMs causing the computed timeout to fire and the process to be killed (non-zero exit); wtype built without protocol support.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/b0613e66bc1a33d8. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/plugins/computer-use/src/backends/linux.mjs:201

    const key = xdotoolKey(rawKey);
    const mods = parts.map((part) => aliases[part] ?? part);
    if (!key || mods.some((mod) => !modifiers.has(mod))) throw new ExecError(`unknown key combination "${text}"`);
    const modKey = aliases[rawKey] ?? rawKey;
    const onlyModifier = modifiers.has(modKey);
    const args = mods.flatMap((mod) => ["-M", mod]);
    for (let i = 0; i < repeat; i++) {
      args.push(onlyModifier ? "-M" : "-P", onlyModifier ? modKey : key);
      if (holdMs) args.push("-s", String(holdMs));
      args.push(onlyModifier ? "-m" : "-p", onlyModifier ? modKey : key);
    }
    args.push(...mods.reverse().flatMap((mod) => ["-m", mod]));
    // wtype owns a temporary Wayland keyboard; the compositor releases its
    // keys on process exit, including cancellation. Keep the complete gesture
    // in one process (https://github.com/atx/wtype#usage).
    throwIfAborted();
    const result = await run("wtype", args, { timeoutMs: Math.max(10_000, holdMs + 8_000) });
    throwIfAborted();
    if (result.code !== 0) throw new ExecError(`wtype exited ${result.code}: ${result.stderr.trim().slice(0, 200)}`, result);
  }

  function assertNum(v, name) {
    const n = Number(v);
    if (!Number.isFinite(n)) throw new ExecError(`${name} must be a finite number`);
    return n;
  }

  // ---------- AT-SPI tree ----------
  const PYATSPI_APP = `def resolve_app(desktop, app_name):
    apps = [desktop.getChildAtIndex(i) for i in range(desktop.childCount)]
    if app_name:
        matches = [app for app in apps if app and app_name.casefold() == (app.name or "").casefold()]
        return matches[0] if len(matches) == 1 else None
    return next((app for app in apps if app and app.childCount), None)
`;
  const PYATSPI_WALK = `import json, sys, pyatspi
${PYATSPI_APP}

View on GitHub (pinned to 73e0f67d83)