Hmbown/CodeWhale · error · ExecError
no screenshot taken yet on this computer — call screenshot…
Error message
no screenshot taken yet on this computer — call screenshot first
What it means
zoom crops a region from the most recent screenshot via ffmpeg, using source ?? lastRaster?.file. The backend keeps no raster from a previous screenshot (or none was ever taken), so there is no image to crop and it refuses rather than silently zooming nothing.
Solutions
- Call screenshot first, then zoom without a source argument.
- Pass an explicit source image path to zoom({ source: '/path/to/shot.png' }).
- Ensure the earlier screenshot call actually succeeded and its path was retained.
- If a session restart wiped the cache, re-take the screenshot rather than reusing an old path.
Example fix
// before
await backend.zoom({ region: [0, 0, 200, 200] }); // throws: no raster yet
// after
await backend.screenshot({});
await backend.zoom({ region: [0, 0, 200, 200] }); Defensive patterns
Strategy: validation
Validate before calling
if (!source && !lastScreenshotPath) throw new Error('call screenshot() before zoom()'); Type guard
const hasRaster = (s) => typeof s === 'string' && s.length > 0;
Try / catch
try {
return await backend.zoom({ region });
} catch (e) {
if (String(e.message).includes('no screenshot taken yet')) {
await backend.screenshot({});
return await backend.zoom({ region });
}
throw e;
} Prevention
- Sequence screenshot before zoom in every automation flow
- Pass an explicit source path instead of relying on lastRaster session state
- Re-take screenshots after session restarts; the cache does not survive
When it happens
Trigger: Calling zoom before any screenshot call in the session; passing no source while lastRaster was cleared (new session/connection); passing a source that is null/undefined after destructure; using zoom on a fresh backend instance.
Common situations: Scripts that zoom first for detail inspection before taking a baseline screenshot; retry logic that re-runs zoom after the session restarted and lost the last-screenshot cache; agent flows where the screenshot step was skipped or failed earlier.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- exited
- linux backend needs " " for — install it and retry
- open_application needs a plain executable/desktop name
- Windows screenshot does not support app_ref or window_id…
- zoom is not supported on the harmony backend yet —…
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/499cef30d432cf3c.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/plugins/computer-use/src/backends/linux.mjs:563
if not ok:
print(json.dumps({"found": True, "element": None, "reason": "element_stale"}))
sys.exit(0)
ext = None
try: ext = node.queryComponent().getExtents(pyatspi.DESKTOP_COORDS)
except Exception: pass
print(json.dumps({"found": True, "reason": None, "element": {
"role": node.getRoleName() or None, "label": node.name or None,
"position": {"x": ext.x, "y": ext.y} if ext else None,
"size": {"w": ext.width, "h": ext.height} if ext else None}}))`;
const r = await run("python3", ["-c", script, name, JSON.stringify(pathArr ?? [])], { timeoutMs: 30_000 });
const out = tryJson(r.stdout.trim().split("\n").pop() ?? "", null);
if (!out) throw new ExecError(`AT-SPI resolve failed: ${(r.stderr || r.stdout).slice(0, 250)}`, r);
return out;
},
zoom: async ({ source, region, path: outPath }) => {
need("ffmpeg", "zoom/crop");
const src = source ?? lastRaster?.file;
if (!src) throw new ExecError("no screenshot taken yet on this computer — call screenshot first");
const out = outputPath(outPath ?? path.join(recordingsDir(), `zoom-${crypto.randomBytes(4).toString("hex")}.png`));
await runOk("ffmpeg", ["-y", "-loglevel", "error", "-i", src, "-vf", `crop=${Math.round(region[2])}:${Math.round(region[3])}:${Math.round(region[0])}:${Math.round(region[1])}`, out], { timeoutMs: 20_000 });
return { file: out, bytes: fs.statSync(out).size, region, source: src };
},
left_click: ({ target, strategy }) => { assertNum(target.x, "x"); assertNum(target.y, "y"); assertEventStrategy(strategy); return inputChain(target.x, target.y, () => clickButton(1, 1)); },
double_click: ({ target }) => inputChain(target.x, target.y, () => clickButton(1, 2)),
triple_click: ({ target }) => inputChain(target.x, target.y, () => clickButton(1, 3)),
right_click: ({ target }) => inputChain(target.x, target.y, () => clickButton(3, 1)),
middle_click: ({ target }) => inputChain(target.x, target.y, () => clickButton(2, 1)),
mouse_move: ({ target }) => inputMove(target.x, target.y),
left_click_drag: async ({ from_target: from, to }) => {
requireInputOwner();
await inputMove(from.x, from.y);
throwIfAborted();
mouseHeld = true;
try {
if (session === "x11") await xdotool(["mousedown", "1"]);
else await ydotool(["click", "0x40"]);View on GitHub (pinned to 73e0f67d83)