Hmbown/CodeWhale · error · ExecError

wtype failed

Error message

wtype failed: ${r.stderr.slice(0, 200)}

What it means

On Wayland, typing is delegated to the wtype utility; this error is thrown when wtype exits non-zero and includes its first 200 bytes of stderr. It means wtype itself failed — not that the backend refused — usually due to missing input-method/virtual-keyboard protocol support or a bad character for the current keymap.

Solutions

  1. Read the stderr excerpt in the message for the exact wtype complaint.
  2. Test manually: wtype -- 'hello' in a terminal on the same session to reproduce.
  3. Ensure the compositor allows the virtual-keyboard protocol and the client can access the Wayland socket (WAYLAND_DISPLAY set, not sandboxed).
  4. Switch keyboard layout or strip unsupported characters; alternatively run the app under XWayland and use the xdotool typing path.

Example fix

// before
await backend.type({ text: 'naïve café' }); // fails under restricted keymap
// after
const ascii = text.normalize('NFD').replace(/[^\x20-\x7E]/g, '');
await backend.type({ text: ascii }); // or fix wtype/Wayland env first
Defensive patterns

Strategy: try-catch

Validate before calling

const chk = spawnSync('wtype', ['--', ''], { env: process.env });
if (chk.error || chk.status !== 0) throw new Error('wtype unavailable or blocked: ' + chk.stderr);

Try / catch

try {
  await backend.type({ text });
} catch (e) {
  if (String(e.message).startsWith('wtype failed:')) {
    // sanitize text or fall back to clipboard paste
    await setClipboard(text);
    await backend.key({ text: 'ctrl+v' });
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: wtype not granted the virtual-keyboard protocol (compositor rejects: sway needs no config but some compositors restrict it; hyprland fine, but sandboxed/flatpak contexts blocked); typing characters not representable in the active keymap/layout; wtype binary present but the session lacks zwp_virtual_keyboard_manager; running under a compositor without virtual keyboard support.

Common situations: Flatpak/sandboxed environments where Wayland protocol sockets are restricted; non-US keymaps choking on specific Unicode; headless Wayland (cage, gamescope) without keyboard protocol; wtype installed but ydotoold/uinput permissions missing — though wtype uses libxkbcommon + virtual keyboard directly.

Related errors


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

Appendix: source

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

          if (chunk.codePointAt(0) > 127) {
            const hex = chunk.codePointAt(0).toString(16).toUpperCase().padStart(4, "0");
            const shift = chunk !== chunk.toLowerCase() ? "shift+" : "";
            await xdotool(["key", `${shift}U${hex}`]);
            // Each temp remap restores the keymap as soon as the event is
            // queued; a lagging app can then read the press against the
            // restored map and drop it. A short settle narrows that window.
            // Under heavy host saturation XTEST drops remain possible — that
            // residual is documented in the suite's known_limitations.
            await new Promise((r) => setTimeout(r, 30));
          } else {
            await xdotool(["type", "--delay", "12", "--", chunk]);
          }
        }
        return { action_sent: true, chars: text.length };
      }
      need("wtype", "typing on Wayland");
      const r = await run("wtype", ["--", String(text)], { timeoutMs: 15_000 });
      if (r.code !== 0) throw new ExecError(`wtype failed: ${r.stderr.slice(0, 200)}`, r);
      return { action_sent: true, chars: text.length };
    },
    key: async ({ text, repeat = 1 }) => {
      await probeSession();
      const k = xdotoolKey(text);
      const n = Math.max(1, Math.min(100, Number(repeat) || 1));
      if (session === "x11") {
        throwIfAborted();
        heldKeys.add(k);
        try {
          await xdotool(["key", "--repeat", String(n), "--delay", "60", k]);
          heldKeys.delete(k);
        } finally { await releaseKey(k); }
      } else await waylandKey(text, { repeat: n });
      return { action_sent: true, key: k };
    },
    hold_key: async ({ text, duration }) => {
      requireInputOwner();

View on GitHub (pinned to 73e0f67d83)