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

The zoom action crops from the most recent screenshot raster cached in state.lastRaster. If no screenshot has been taken in this session (or no explicit `source` is given), there is no raster to crop and the library throws. A `source` path bypasses this requirement.

Solutions

  1. Call screenshot() before zoom() in the same session.
  2. Pass an explicit `source` image path to zoom({ source, region }) instead of relying on the cached raster.
  3. Re-establish state if the backend was restarted, then screenshot first.

Example fix

// before
await zoom({ region: [0, 0, 100, 100] }); // fresh session
// after
await screenshot();
await zoom({ region: [0, 0, 100, 100] });
Defensive patterns

Strategy: try-catch

Try / catch

try { await zoom({ region }) } catch (e) {
  if (String(e.message).includes("no screenshot taken yet")) {
    await screenshot();
    await zoom({ region });
  } else throw e;
}

Prevention

When it happens

Trigger: Calling zoom({ region }) without first calling screenshot() in the same backend session; after a backend restart/state reset that cleared lastRaster; calling zoom on a fresh connection while the screenshot was taken in a different session.

Common situations: An agent starts a new session and immediately calls zoom assuming previous state; the computer-use backend process was restarted between the screenshot and the zoom call.

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


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

Appendix: source

Thrown at crates/tui/plugins/computer-use/src/backends/darwin.mjs:572

    state.lastRaster = {
      file,
      ...(window ? { app_ref, window_index: window_id ?? 0 } : {}),
      bytes: stat.size,
      display: disp ?? 1,
      // Region and window rasters describe that rect, not the whole display.
      // The PNG header is the pixel ground truth; scale is derived from
      // pixels/points below so Retina and mixed-DPI stay exact.
      points: window?.points ?? (region ? { x: region[0], y: region[1], w: region[2], h: region[3] } : d?.points ?? null),
      pixels: imagePixels(file),
      scale: d?.scale ?? 1,
      capturedAt: new Date().toISOString(),
    };
    if (state.lastRaster.points?.w) state.lastRaster.scale = state.lastRaster.pixels.w / state.lastRaster.points.w;
    return { ...state.lastRaster, path: file };
  }

  async function zoom({ source, region, path: outPath }) {
    if (!source && !state.lastRaster) throw new ExecError("no screenshot taken yet on this computer — call screenshot first");
    const [x, y, w, h] = region;
    if (![x, y, w, h].every((n) => Number.isInteger(n) && n >= 0) || !w || !h || x + w > state.lastRaster.pixels.w || y + h > state.lastRaster.pixels.h) throw new ExecError("region must be [x, y, w, h] in last-raster pixels");
    const src = source ?? state.lastRaster.file;
    const dir = recordingsDir();
    fs.mkdirSync(dir, { recursive: true });
    const out = outPath || path.join(dir, `zoom-${crypto.randomBytes(4).toString("hex")}.png`);
    await runOk("sips", ["-s", "format", "png", "-c", String(Math.round(h)), String(Math.round(w)), "--cropOffset", String(Math.round(y)), String(Math.round(x)), src, "--out", out], { timeoutMs: 15_000 });
    const parent = state.lastRaster;
    state.lastRaster = { file: out, bytes: fs.statSync(out).size, source: src, region,
      points: { x: (parent.points?.x ?? 0) + x / parent.scale, y: (parent.points?.y ?? 0) + y / parent.scale, w: w / parent.scale, h: h / parent.scale },
      pixels: { w, h }, scale: parent.scale, capturedAt: new Date().toISOString() };
    return state.lastRaster;
  }

  // ---------- recording ----------
  const rec = new Map(); // Includes starting children so session close owns them too.

  function requestRecordingStop(r) {

View on GitHub (pinned to 73e0f67d83)