{"record":{"id":"0fd1c878fa12ce2a","repo":"Hmbown/CodeWhale","slug":"cmd-exited-r-code-r-stderr-trim-slice-0-300","errorCode":null,"errorMessage":"${cmd} exited ${r.code}: ${r.stderr.trim().slice(0, 300)}","messagePattern":"(.+?) exited (.+?): (.+?)","errorType":"exception","errorClass":"ExecError","httpStatus":null,"severity":"error","filePath":"crates/tui/plugins/computer-use/src/backends/linux.mjs","lineNumber":143,"sourceCode":"  }\n\n  /** Capture a PNG to `file`, optionally cropped to region [x,y,w,h] points. */\n  async function takeShot(file, region) {\n    outputPath(file);\n    const { cmd, base } = await shotTool();\n    let args = [...base];\n    if (cmd === \"grim\") {\n      if (region) args.push(\"-g\", `${Math.round(region[0])},${Math.round(region[1])} ${Math.round(region[2])}x${Math.round(region[3])}`);\n      args.push(file);\n    } else if (cmd === \"scrot\") {\n      if (region) args.push(\"-a\", `${Math.round(region[0])},${Math.round(region[1])},${Math.round(region[2])},${Math.round(region[3])}`);\n      args.push(file);\n    } else {\n      if (region) args.push(\"-crop\", `${Math.round(region[2])}x${Math.round(region[3])}+${Math.round(region[0])}+${Math.round(region[1])}`);\n      args.push(file);\n    }\n    const r = await run(cmd, args, { timeoutMs: 10_000 });\n    if (r.code !== 0) throw new ExecError(`${cmd} exited ${r.code}: ${r.stderr.trim().slice(0, 300)}`, r);\n  }\n\n  function recordingsDir() {\n    return path.resolve(process.env.CODEWHALE_CU_RECORDINGS_DIR || path.join(os.homedir(), \".codewhale-cu\", \"recordings\"));\n  }\n\n  async function xdotool(args, opts = {}) {\n    need(\"xdotool\", \"input on X11\");\n    throwIfAborted();\n    const r = await run(\"xdotool\", args, opts);\n    throwIfAborted();\n    if (r.code !== 0) throw new ExecError(`xdotool ${args[0]} exited ${r.code}: ${r.stderr.trim().slice(0, 200)}`, r);\n    return r.stdout.trim();\n  }\n\n  async function ydotool(args, opts = {}) {\n    need(\"ydotool\", \"input on Wayland (ydotool needs its daemon running: sudo ydotoold)\");\n    throwIfAborted();","sourceCodeStart":125,"sourceCodeEnd":161,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/plugins/computer-use/src/backends/linux.mjs#L125-L161","documentation":"This ExecError is thrown by the Linux screenshot helper when an external screenshot command (e.g. import, scrot, gnome-screenshot, or ImageMagick convert for cropping) exits with a non-zero status. The library wraps the command's exit code and the first 300 bytes of stderr so the developer can see exactly why the external tool failed. It is a deliberate failure surfaced from the subprocess, not an internal bug.","triggerScenarios":"Calling takeShot (public screenshot action) when the resolved screenshot command fails: tool missing from PATH is caught earlier by `need`, but a present-yet-broken tool (no DISPLAY, Wayland without portal support, invalid -crop region geometry, unwritable output file) exits non-zero and triggers this throw.","commonSituations":"Running under Wayland with an X11-only tool like `import`; missing X authority in headless/SSH sessions; crop region coordinates outside the screen or negative after Math.round; disk full or HOME not writable so the temp file cannot be created.","solutions":["Read the stderr tail in the message: it names the failing tool and reason; fix that tool's environment first (DISPLAY/WAYLAND_DISPLAY/XAUTHORITY).","Verify the screenshot binary works standalone with the same args (e.g. run `import -window root /tmp/x.png`).","Under Wayland, install/switch to a Wayland-capable tool (grim, gnome-screenshot, spectacle) or use the XWayland/XDG portal path.","If a crop region was passed, validate x/y/w/h are non-negative and within the virtual screen bounds before calling.","Check disk space and write permissions for the temp screenshot file location."],"exampleFix":"// before\nawait takeShot({ region: [-50, -20, 800, 600] });\n// after\nconst region = [Math.max(0, x), Math.max(0, y), w, h];\nawait takeShot({ region });","handlingStrategy":"fallback","validationCode":"import { execFileSync } from 'child_process';\nfunction canScreenshot(cmd) {\n  try { execFileSync('sh', ['-c', `command -v ${cmd}`], { stdio: 'ignore' }); return true; } catch { return false; }\n}\nconst regionOk = (r) => !r || (r.every(Number.isFinite) && r[0] >= 0 && r[1] >= 0 && r[2] > 0 && r[3] > 0);","typeGuard":"const isValidRegion = (r) => Array.isArray(r) && r.length === 4 && r.every((n) => Number.isFinite(n) && n >= 0);","tryCatchPattern":"try {\n  const shot = await takeShot({ region });\n} catch (e) {\n  if (e instanceof ExecError && /import|scrot|convert/.test(e.message)) {\n    // switch to a Wayland-capable tool or fix DISPLAY and retry once\n    const shot = await takeShot({});\n  } else throw e;\n}","preventionTips":["Install a session-appropriate screenshot tool (grim on Wayland, scrot/import on X11) and smoke-test it with the same args.","Keep DISPLAY/XAUTHORITY correct when connecting over SSH or from containers.","Validate crop regions: finite, non-negative, within screen bounds.","Check free disk space where temp screenshots are written.","Prefer running the plugin inside the desktop session's user environment."],"tags":["subprocess","screenshot","linux","external-tool"],"backgroundTag":"external-command-failed","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-22T21:17:16.096Z"}