Hmbown/CodeWhale · error · ExecError

linux backend needs " " for — install it and retry

Error message

linux backend needs "${tool}" for ${purpose} — install it and retry

What it means

The Linux backend shells out to external tools (grim, scrot, import, etc.) for screenshots and input. `need(tool, purpose)` checks the tool-availability probe and throws when a required tool is missing, naming the tool and the purpose so the user knows what to install.

Solutions

  1. Install the named tool: on Wayland `apt install grim`; on X11 `apt install scrot` or `imagemagick`.
  2. Match the tool to your session type (grim for Wayland, scrot/import for X11).
  3. In containers, add the tool to the image and ensure the process has access to the display.

Example fix

// before
await backend.screenshot({ file: "/tmp/s.png" }); // linux backend needs "grim" for screenshots on Wayland
// after
// $ sudo apt install grim
await backend.screenshot({ file: "/tmp/s.png" });
Defensive patterns

Strategy: validation

Validate before calling

const { execFile } = await import("node:child_process");
const need = process.env.WAYLAND_DISPLAY ? "grim" : "scrot";
await new Promise((res, rej) => execFile(need, ["--help"], { stdio: "ignore" }, (e) => e && e.code !== 0 ? rej(new Error(`${need} missing`)) : res()));

Type guard

null

Try / catch

try { await backend.screenshot({ file }); }
catch (e) {
  const m = String(e.message).match(/needs "([^"]+)" for (.+?) —/);
  if (m) throw new Error(`Install missing tool: ${m[1]} (${m[2]})`);
  throw e;
}

Prevention

When it happens

Trigger: Taking a screenshot on Wayland without `grim`, or on X11 without `scrot`/`import` (imagemagick), or any other action whose backing tool is not installed (backends/linux.mjs:114).

Common situations: Fresh Linux installs or slim Docker images without screenshot utilities; Wayland sessions where X11 tools like scrot don't apply and grim was never installed; switching from X11 to Wayland between runs.

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/08346b2c2be2e3ee. Report an issue: GitHub.

Appendix: source

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

    const wayland = !!(process.env.WAYLAND_DISPLAY || process.env.XDG_SESSION_TYPE === "wayland");
    const x11 = !!(process.env.DISPLAY || process.env.XDG_SESSION_TYPE === "x11");
    session = wayland && !x11 ? "wayland" : x11 ? "x11" : null;
    if (session === null) {
      const e = new ExecError("no X11 ($DISPLAY) or Wayland ($WAYLAND_DISPLAY) session visible to this process — set DISPLAY or run inside the desktop session");
      e.code = "no_session";
      throw e;
    }
    for (const t of ["xdotool", "wmctrl", "scrot", "import", "grim", "slurp", "wtype", "ydotool", "wf-recorder", "ffmpeg", "xclip", "xsel", "wl-copy", "wl-paste", "python3", "xrandr", "swaymsg", "hyprctl"]) {
      tools[t] = await have(t);
    }
    tools.pyatspi = tools.python3 && (await run("python3", ["-c", "import pyatspi"], { timeoutMs: 10_000 })).code === 0;
    throwIfAborted();
    probed = true;
    return session;
  }

  function need(tool, purpose) {
    if (!tools[tool]) throw new ExecError(`linux backend needs "${tool}" for ${purpose} — install it and retry`);
  }

  async function shotTool() {
    if (session === "wayland") { need("grim", "screenshots on Wayland"); return { cmd: "grim", base: [] }; }
    if (session === "x11") {
      if (tools.scrot) return { cmd: "scrot", base: ["-z"] };
      need("import", "screenshots on X11 (imagemagick)");
      return { cmd: "import", base: ["-window", "root"] };
    }
    throw new ExecError("no X11 ($DISPLAY) or Wayland ($WAYLAND_DISPLAY) session visible to this process");
  }

  /** Capture a PNG to `file`, optionally cropped to region [x,y,w,h] points. */
  async function takeShot(file, region) {
    outputPath(file);
    const { cmd, base } = await shotTool();
    let args = [...base];
    if (cmd === "grim") {

View on GitHub (pinned to 73e0f67d83)