Hmbown/CodeWhale · error · ExecError

zoom failed

Error message

zoom failed: ${(r.stderr || "").slice(0, 250)}

What it means

The win32 backend's zoom action runs a PowerShell script that captures a screen region with System.Drawing and saves a PNG. If the PowerShell process exits non-zero or the output file does not appear, the backend throws this ExecError with the first 250 characters of PowerShell's stderr attached. It signals that the native screenshot/zoom pipeline failed, not that the requested region was invalid.

Solutions

  1. Check the error's stderr/result payload (r is attached to the ExecError) for the actual PowerShell message and fix the underlying cause (display unavailable, path, permissions).
  2. Ensure an interactive desktop session exists — reconnect RDP or run in a logged-in console session before retrying zoom.
  3. Verify the output/recordings directory is writable and has free disk space.
  4. Retry once; transient GDI failures during display changes often resolve on a second attempt.
  5. Fall back to the plain screenshot action if zoom of a sub-region keeps failing.

Example fix

// caller before
const shot = await backend.zoom({ region });
// after
let shot;
try {
  shot = await backend.zoom({ region });
} catch (e) {
  console.error('zoom stderr:', e.result?.stderr);
  shot = await backend.screenshot({}); // fallback to full screen
}
Defensive patterns

Strategy: try-catch

Validate before calling

// ensure output dir writable and session has a desktop before zoom
if (!fs.existsSync(recordingsDir())) throw new Error('output dir missing');

Type guard

const isZoomError = (e) => e instanceof Error && String(e.message).startsWith('zoom failed:');

Try / catch

try {
  return await backend.zoom({ region });
} catch (e) {
  if (isZoomError(e)) return await backend.screenshot({}); // full-screen fallback
  throw e;
}

Prevention

When it happens

Trigger: Calling zoom on the win32 backend when: the embedded PowerShell script throws (e.g. System.Drawing assembly fails to load, $rect has zero/negative dimensions), $bmp.Save fails (path locked, disk full, invalid output dir), the 20s timeout elapses, or fs.existsSync(out) is false after a nominally successful run.

Common situations: Windows sessions without a desktop (RDP disconnected, service context) where GDI screen capture returns empty; antivirus or policy blocking PowerShell; the temp output directory being cleaned mid-run; screen locked so the captured bitmap is invalid.

Related errors


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

Appendix: source

Thrown at crates/tui/plugins/computer-use/src/backends/win32.mjs:441

      lastRaster = { file, bytes: fs.statSync(file).size, points: { x: meta.x, y: meta.y, w: meta.w, h: meta.h }, pixels: { w: meta.w, h: meta.h }, scale: 1, capturedAt: new Date().toISOString() };
      return { ...lastRaster };
    },
    zoom: async ({ source, region, path: outPath }) => {
      const src = source ?? lastRaster?.file;
      if (!src) throw new ExecError("no screenshot taken yet on this computer — call screenshot first");
      const out = outPath || path.join(recordingsDir(), `zoom-${crypto.randomBytes(4).toString("hex")}.png`);
      const script = `Add-Type -AssemblyName System.Drawing;
$img = [System.Drawing.Image]::FromFile('${src.replace(/'/g, "''")}');
$rect = New-Object System.Drawing.Rectangle(${Math.round(region[0])}, ${Math.round(region[1])}, ${Math.round(region[2])}, ${Math.round(region[3])});
$bmp = New-Object System.Drawing.Bitmap($rect.Width, $rect.Height);
$g = [System.Drawing.Graphics]::FromImage($bmp);
$g.DrawImage($img, (New-Object System.Drawing.Rectangle(0, 0, $rect.Width, $rect.Height)), $rect, [System.Drawing.GraphicsUnit]::Pixel);
$g.Dispose();
$bmp.Save('${out.replace(/'/g, "''")}', [System.Drawing.Imaging.ImageFormat]::Png);
$bmp.Dispose(); $img.Dispose();
Write-Output '{"ok": true}';`;
      const r = await psOk(script, { timeoutMs: 20_000 });
      if (r.code !== 0 || !fs.existsSync(out)) throw new ExecError(`zoom failed: ${(r.stderr || "").slice(0, 250)}`, r);
      return { file: out, bytes: fs.statSync(out).size, region, source: src };
    },
    left_click: ({ target, strategy }) => { assertEventStrategy(strategy); return clickAt(0, target.x, target.y, 1); },
    double_click: ({ target }) => clickAt(0, target.x, target.y, 2),
    triple_click: ({ target }) => clickAt(0, target.x, target.y, 3),
    right_click: ({ target }) => clickAt(1, target.x, target.y, 1),
    middle_click: ({ target }) => clickAt(2, target.x, target.y, 1),
    mouse_move: async ({ target }) => {
      await withUser32(`[User32]::SetCursorPos(${Math.round(target.x)}, ${Math.round(target.y)}) | Out-Null; Write-Output '{"ok": true}'`);
      return { action_sent: true, at: { x: target.x, y: target.y } };
    },
    left_click_drag: async ({ from_target: from, to }) => {
      requireInputOwner();
      throwIfAborted();
      heldButtons.add("LEFT");
      try {
      await withUser32(`[User32]::SetCursorPos(${Math.round(from.x)}, ${Math.round(from.y)}) | Out-Null;
Start-Sleep -Milliseconds 80;

View on GitHub (pinned to 73e0f67d83)