Hmbown/CodeWhale · error · ExecError

scroll on Wayland is not available in this build; use…

Error message

scroll on Wayland is not available in this build; use swipe-style drags or run an X11/XWayland window. (Roadmap: ydotool wheel events.)

What it means

On X11, scroll is implemented with xdotool mouse-button 4/5/6/7 clicks. On Wayland this build has no wheel-event synthesis wired (ydotool wheel support is a roadmap item), so the library fails honestly instead of faking a scroll. There is no Wayland implementation to fall back to.

Solutions

  1. Run the target app under XWayland and scroll inside that window, or force the app to X11 via env GDK_BACKEND=x11 / QT_QPA_PLATFORM=xcb.
  2. Use swipe-style pointer drags (move_to/left_click-drag sequences) to approximate scrolling.
  3. Install and wire ydotool with a running ydotoold if your build supports wheel events, or upgrade when the roadmap lands.
  4. Switch the session to X11 (login X11 session) if scrolling is essential.

Example fix

// before
await backend.scroll({ direction: 'down', amount: 5 }); // throws on Wayland
// after
if (sessionType !== 'x11') {
  await backend.move_to({ x: cx, y: cy });
  await dragSwipe({ dy: 120 }); // swipe-style drag instead
} else {
  await backend.scroll({ direction: 'down', amount: 5 });
}
Defensive patterns

Strategy: fallback

Validate before calling

const sess = await detectSession(); // 'x11' | 'wayland'
if (sess !== 'x11' && action === 'scroll') useSwipeDragInstead();

Try / catch

try {
  await backend.scroll({ direction, amount });
} catch (e) {
  if (String(e.message).includes('scroll on Wayland')) {
    await swipeDragFallback({ direction, amount });
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling scroll (any direction/amount) while the detected session is Wayland (sway, hyprland, GNOME-Wayland, KDE-Wayland); X11-only branch not taken because probeSession() reported non-x11.

Common situations: Automation scripts that work on an X11 desktop failing on the developer's Wayland laptop; CI containers running Wayland compositors; mixed fleets where the same action script runs on both session types.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


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

Appendix: source

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

        if (session === "x11") await xdotool(["mousedown", "1"]);
        else await ydotool(["click", "0x40"]);
      } catch (err) { await releaseMouse(); throw err; }
      return { action_sent: true };
    },
    left_mouse_up: async () => {
      if (!mouseHeld) throw Object.assign(new ExecError("no agent pointer press to release"), { code: "input_not_held" });
      await releaseMouse();
      return { action_sent: true };
    },
    scroll: async ({ target, direction = "down", amount = 3 }) => {
      await inputMove(target.x, target.y);
      if (session === "x11") {
        const buttons = { down: 5, up: 4, right: 7, left: 6 };
        await xdotool(["click", "--repeat", String(Math.max(1, Math.min(30, amount))), "--delay", "60", String(buttons[direction] ?? 5)]);
        return { action_sent: true, direction, amount };
      }
      // Wayland: synthesize wheel via ydotool is not wired in this build — honest refusal.
      throw new ExecError('scroll on Wayland is not available in this build; use swipe-style drags or run an X11/XWayland window. (Roadmap: ydotool wheel events.)');
    },
    type: async ({ text }) => {
      if (!text) return { action_sent: false, note: "empty text" };
      await probeSession();
      if (session === "x11") {
        // xdotool `type` remaps a spare keycode for characters absent from the
        // current keymap. Two failure modes follow: a cased letter produces a
        // single-symbol key whose XKB level 0 is the lowercase form (Ü → ü),
        // and consecutive remaps inside one `type` call race the X server's
        // keymap-change propagation, so non-ASCII chars intermittently drop or
        // arrive mangled (héllo → hllo, 日本 → 本). Route every non-ASCII char
        // through `key U<hex>` — one synchronous remap+press+restore per char —
        // adding Shift only when the char is cased-uppercase, and batch ASCII
        // runs through `type` as before.
        let runText = "";
        const chunks = [];
        for (const ch of String(text)) {
          if (ch.codePointAt(0) > 127) {

View on GitHub (pinned to 73e0f67d83)