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
- 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).
- Ensure an interactive desktop session exists — reconnect RDP or run in a logged-in console session before retrying zoom.
- Verify the output/recordings directory is writable and has free disk space.
- Retry once; transient GDI failures during display changes often resolve on a second attempt.
- 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
- Confirm an interactive desktop session (RDP connected, not locked) before screen capture calls.
- Pre-check output directory writability and free disk space.
- Inspect e.result.stderr on every zoom failure to learn the PowerShell-level cause.
- Keep a screenshot fallback wired in for capture pipeline failures.
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
- screenshot did not return a valid raster
- screenshot failed
- no screenshot taken yet on this computer — call screenshot…
- powershell did not return JSON
- powershell.exe exited
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)