Hmbown/CodeWhale · error · ExecError
no X11 ($DISPLAY) or Wayland ($WAYLAND_DISPLAY) session…
Error message
no X11 ($DISPLAY) or Wayland ($WAYLAND_DISPLAY) session visible to this process
What it means
`shotTool` picks a screenshot tool based on the detected session ("wayland" or "x11"). If neither $WAYLAND_DISPLAY nor $DISPLAY is set/visible to this process, no screenshot mechanism can work, so it throws instead of guessing. This is an environment-detection failure, not a tool failure.
Solutions
- Run the backend from within a graphical session shell so DISPLAY or WAYLAND_DISPLAY is set.
- For SSH, use `ssh -X` or run the automation on the machine with the GUI session.
- In containers/systemd, pass through the display env and socket (e.g. -e WAYLAND_DISPLAY -v $XDG_RUNTIME_DIR:/tmp/xdg).
- Verify with `echo $DISPLAY $WAYLAND_DISPLAY` in the same environment the backend runs in.
Example fix
// before
// CI container: neither DISPLAY nor WAYLAND_DISPLAY set -> throws
// after
// docker run -e WAYLAND_DISPLAY=$WAYLAND_DISPLAY -v $XDG_RUNTIME_DIR/$WAYLAND_DISPLAY:/tmp/$WAYLAND_DISPLAY ...
await backend.screenshot({ file: "/tmp/s.png" }); Defensive patterns
Strategy: validation
Validate before calling
if (!process.env.DISPLAY && !process.env.WAYLAND_DISPLAY)
throw new Error("no graphical session: set DISPLAY or WAYLAND_DISPLAY"); Type guard
const hasGraphicalSession = Boolean(process.env.DISPLAY || process.env.WAYLAND_DISPLAY);
Try / catch
try { await backend.screenshot({ file }); }
catch (e) {
if (String(e.message).includes("no X11") && String(e.message).includes("Wayland"))
throw new Error("Run inside a graphical session or export DISPLAY/WAYLAND_DISPLAY");
throw e;
} Prevention
- Launch automation from within the GUI session
- Use ssh -X or forward the Wayland socket for remote runs
- Pass display env/sockets into containers and services
- Check DISPLAY/WAYLAND_DISPLAY in startup diagnostics
When it happens
Trigger: Calling any screenshot/capture API on the Linux backend when the process has no DISPLAY or WAYLAND_DISPLAY environment variable (e.g. running via SSH without X forwarding, a systemd service, a cron job, or a container without the socket mounted) (backends/linux.mjs:124).
Common situations: Running the agent from a headless shell/CI; SSH session without `ssh -X`; Wayland user session but process launched before the session env is imported; container without passing through the display socket and env.
Understand the failure class
Background: "environment variable is not set" and "Missing keys in environment" errors: what missing required env var messages mean and how to fix them — this error's family across 28 libraries.
Related errors
- clipboard read failed
- cursor position needs an X11 session in this build
- linux backend needs " " for — install it and retry
- PRIMARY selection busy or unavailable
- scroll on Wayland is not available in this build; use…
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/4e93682db4d454cf.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/plugins/computer-use/src/backends/linux.mjs:124
}
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") {
if (region) args.push("-g", `${Math.round(region[0])},${Math.round(region[1])} ${Math.round(region[2])}x${Math.round(region[3])}`);
args.push(file);
} else if (cmd === "scrot") {
if (region) args.push("-a", `${Math.round(region[0])},${Math.round(region[1])},${Math.round(region[2])},${Math.round(region[3])}`);
args.push(file);
} else {
if (region) args.push("-crop", `${Math.round(region[2])}x${Math.round(region[3])}+${Math.round(region[0])}+${Math.round(region[1])}`);
args.push(file);
}
const r = await run(cmd, args, { timeoutMs: 10_000 });View on GitHub (pinned to 73e0f67d83)