Hmbown/CodeWhale · error · ExecError
display enumeration needs xrandr (X11) or swaymsg/hyprctl…
Error message
display enumeration needs xrandr (X11) or swaymsg/hyprctl (Wayland) — install one and retry
What it means
Thrown by the list_displays capability when no supported display-enumeration tool is available for the detected session: xrandr on X11, or swaymsg/hyprctl on Wayland. The backend probes session and tool availability first; if none of the expected binaries are present it fails with this explicit install hint rather than returning an empty list.
Solutions
- On X11 install xrandr (x11-xserver-utils on Debian/Ubuntu) and ensure DISPLAY is set.
- On sway install swaymsg (part of sway); on hyprland ensure hyprctl (part of hyprland) is in PATH.
- On other Wayland compositors (GNOME/KDE), use an XWayland session or add a supported enumeration tool; this backend only enumerates via xrandr/swaymsg/hyprctl.
- Verify which session was detected by checking for the binaries: `command -v xrandr swaymsg hyprctl`.
- For headless CI, install xrandr alongside a virtual X server (xvfb + x11-xserver-utils).
Example fix
// before (Debian, X11 minimal) # xrandr: command not found // after sudo apt install x11-xserver-utils
Defensive patterns
Strategy: fallback
Validate before calling
import { execFileSync } from 'child_process';
const has = (cmd) => { try { execFileSync('sh', ['-c', `command -v ${cmd}`], { stdio: 'ignore' }); return true; } catch { return false; } };
function displayEnumAvailable(session) {
return session === 'x11' ? has('xrandr') : (has('swaymsg') || has('hyprctl'));
} Try / catch
try {
const displays = await listDisplays();
} catch (e) {
if (e instanceof ExecError && e.message.includes('display enumeration needs')) {
// degrade: return a single synthetic full-screen display
return [{ index: 1, name: 'screen', points: await virtualScreenBounds(), main: true }];
}
throw e;
} Prevention
- Install x11-xserver-utils (xrandr) on X11 hosts, including CI images with Xvfb.
- On sway/hyprland keep the compositor's CLI (swaymsg/hyprctl) on PATH.
- For GNOME/KDE Wayland, plan for the limitation: this backend enumerates only via xrandr/swaymsg/hyprctl.
- Check `command -v xrandr swaymsg hyprctl` in preflight and fail with install guidance early.
- Keep the plugin's PATH aligned with the desktop session's environment.
When it happens
Trigger: Calling list_displays on a system where: X11 lacks xrandr (minimal X installs, some containers), or Wayland is neither sway (no swaymsg) nor hyprland (no hyprctl) — e.g. GNOME Wayland without any of the tools, or tools absent from PATH.
Common situations: Docker/CI images with bare Xvfb and no xrandr; GNOME/KDE Wayland sessions (this backend supports sway/hyprctl enumeration there, and neither tool exists); stripped-down embedded or minimal arch installs.
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
- list_windows needs wmctrl (X11), swaymsg (sway) or hyprctl…
- clipboard read failed
- cursor position needs an X11 session in this build
- linux backend needs " " for — install it and retry
- list_apps needs wmctrl (X11) or swaymsg/hyprctl (Wayland)
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/8050b57c708651ac.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/plugins/computer-use/src/backends/linux.mjs:406
displays.push({ index: i++, name: m[1], points: { x: Number(m[4]), y: Number(m[5]), w: Number(m[2]), h: Number(m[3]) }, pixels: { w: Number(m[2]), h: Number(m[3]) }, scale: 1, main: /primary/.test(m[0]) || i === 1 });
}
if (displays.length) return displays;
}
if (session === "wayland" && tools.swaymsg) {
const r = await run("swaymsg", ["-t", "get_outputs", "-r"], { timeoutMs: 15_000 });
const outs = tryJson(r.stdout, []);
if (Array.isArray(outs) && outs.length) {
return outs.map((o, i) => ({ index: i + 1, name: o.name, points: { x: o.rect?.x, y: o.rect?.y, w: o.rect?.width, h: o.rect?.height }, pixels: { w: o.current_mode?.width, h: o.current_mode?.height }, scale: o.scale ?? 1, main: i === 0 }));
}
}
if (session === "wayland" && tools.hyprctl) {
const r = await run("hyprctl", ["-j", "monitors"], { timeoutMs: 15_000 });
const ms = tryJson(r.stdout, []);
if (Array.isArray(ms) && ms.length) {
return ms.map((o, i) => ({ index: i + 1, name: o.name, points: { x: o.x, y: o.y, w: o.width, h: o.height }, pixels: { w: o.width, h: o.height }, scale: o.scale ?? 1, main: !!o.main || i === 0 }));
}
}
throw new ExecError("display enumeration needs xrandr (X11) or swaymsg/hyprctl (Wayland) — install one and retry");
},
switch_display: async ({ index }) => ({ activeDisplay: index ?? 1, note: "linux screenshots grab the compositor's virtual screen; per-display selection applies only where the shot tool supports it" }),
list_apps: async () => {
await probeSession();
if (session === "x11" && tools.wmctrl) {
const r = await runOk("wmctrl", ["-lx"], { timeoutMs: 15_000 });
const seen = new Map();
for (const line of r.stdout.split("\n")) {
const parts = line.split(/\s+/);
const wmClass = parts[2];
if (wmClass) seen.set(wmClass, { name: wmClass.split(".")[0], wm_class: wmClass });
}
return { apps: [...seen.values()] };
}
if (session === "wayland" && (tools.swaymsg || tools.hyprctl)) {
const w = await this.list_windows();
const seen = new Map();
for (const win of w.windows) {View on GitHub (pinned to 73e0f67d83)