{"record":{"id":"499cef30d432cf3c","repo":"Hmbown/CodeWhale","slug":"no-screenshot-taken-yet-on-this-computer-call-screenshot-499cef","errorCode":null,"errorMessage":"no screenshot taken yet on this computer — call screenshot first","messagePattern":"no screenshot taken yet on this computer — call screenshot first","errorType":"exception","errorClass":"ExecError","httpStatus":null,"severity":"error","filePath":"crates/tui/plugins/computer-use/src/backends/linux.mjs","lineNumber":563,"sourceCode":"if not ok:\n    print(json.dumps({\"found\": True, \"element\": None, \"reason\": \"element_stale\"}))\n    sys.exit(0)\next = None\ntry: ext = node.queryComponent().getExtents(pyatspi.DESKTOP_COORDS)\nexcept Exception: pass\nprint(json.dumps({\"found\": True, \"reason\": None, \"element\": {\n    \"role\": node.getRoleName() or None, \"label\": node.name or None,\n    \"position\": {\"x\": ext.x, \"y\": ext.y} if ext else None,\n    \"size\": {\"w\": ext.width, \"h\": ext.height} if ext else None}}))`;\n      const r = await run(\"python3\", [\"-c\", script, name, JSON.stringify(pathArr ?? [])], { timeoutMs: 30_000 });\n      const out = tryJson(r.stdout.trim().split(\"\\n\").pop() ?? \"\", null);\n      if (!out) throw new ExecError(`AT-SPI resolve failed: ${(r.stderr || r.stdout).slice(0, 250)}`, r);\n      return out;\n    },\n    zoom: async ({ source, region, path: outPath }) => {\n      need(\"ffmpeg\", \"zoom/crop\");\n      const src = source ?? lastRaster?.file;\n      if (!src) throw new ExecError(\"no screenshot taken yet on this computer — call screenshot first\");\n      const out = outputPath(outPath ?? path.join(recordingsDir(), `zoom-${crypto.randomBytes(4).toString(\"hex\")}.png`));\n      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 });\n      return { file: out, bytes: fs.statSync(out).size, region, source: src };\n    },\n    left_click: ({ target, strategy }) => { assertNum(target.x, \"x\"); assertNum(target.y, \"y\"); assertEventStrategy(strategy); return inputChain(target.x, target.y, () => clickButton(1, 1)); },\n    double_click: ({ target }) => inputChain(target.x, target.y, () => clickButton(1, 2)),\n    triple_click: ({ target }) => inputChain(target.x, target.y, () => clickButton(1, 3)),\n    right_click: ({ target }) => inputChain(target.x, target.y, () => clickButton(3, 1)),\n    middle_click: ({ target }) => inputChain(target.x, target.y, () => clickButton(2, 1)),\n    mouse_move: ({ target }) => inputMove(target.x, target.y),\n    left_click_drag: async ({ from_target: from, to }) => {\n      requireInputOwner();\n      await inputMove(from.x, from.y);\n      throwIfAborted();\n      mouseHeld = true;\n      try {\n        if (session === \"x11\") await xdotool([\"mousedown\", \"1\"]);\n        else await ydotool([\"click\", \"0x40\"]);","sourceCodeStart":545,"sourceCodeEnd":581,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/plugins/computer-use/src/backends/linux.mjs#L545-L581","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nawait backend.zoom({ region: [0, 0, 200, 200] }); // throws: no raster yet\n// after\nawait backend.screenshot({});\nawait backend.zoom({ region: [0, 0, 200, 200] });","handlingStrategy":"validation","validationCode":"if (!source && !lastScreenshotPath) throw new Error('call screenshot() before zoom()');","typeGuard":"const hasRaster = (s) => typeof s === 'string' && s.length > 0;","tryCatchPattern":"try {\n  return await backend.zoom({ region });\n} catch (e) {\n  if (String(e.message).includes('no screenshot taken yet')) {\n    await backend.screenshot({});\n    return await backend.zoom({ region });\n  }\n  throw e;\n}","preventionTips":["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"],"tags":["linux","computer-use","screenshot","state-dependency","ordering"],"backgroundTag":"invalid-state-transition","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-22T16:17:23.217Z"}