Hmbown/CodeWhale · error · ExecError

the selected application has no capturable window — call…

Error message

the selected application has no capturable window — call list_windows

What it means

Before recording a specific application's window, the record action resolves the app via native window_info. If the resolved window has no points or a zero-width/height rect, there is nothing capturable to record and the library throws, directing the caller to list_windows.

Solutions

  1. Call list_windows to get currently capturable windows and use a valid app_ref or window_id from it.
  2. Un-minimize the target app or bring it on-screen so it has a non-zero window rect.
  3. Record a display or region instead of an app window if the app has no GUI window.
  4. Re-resolve app_ref after the target application was restarted.

Example fix

// before
await record({ app_ref: staleRef });
// after
const wins = await list_windows();
await record({ app_ref: wins[0].app_ref });
Defensive patterns

Strategy: fallback

Validate before calling

const wins = await list_windows();
const target = wins.find(w => w.app_ref === appRef || w.window_id === windowId);
if (!target || !target.points || !(target.points.w > 0) || !(target.points.h > 0))
  throw new Error("target app has no capturable window");

Type guard

const isCapturable = (w) => w?.points && w.points.w > 0 && w.points.h > 0;

Try / catch

try { await record({ app_ref }) } catch (e) {
  if (String(e.message).includes("no capturable window")) {
    const wins = await list_windows();
    if (wins.length) await record({ app_ref: wins[0].app_ref });
  } else throw e;
}

Prevention

When it happens

Trigger: Calling record({ app_ref }) for an app with no on-screen windows (all minimized or only background windows); passing a window_id/app_ref that no longer exists; the app has a window whose rect reports zero size; stale app_ref after the target app quit.

Common situations: Target app is minimized to the Dock; targeting helper/background apps (menu-bar-only apps have no capturable window); the app crashed or was relaunched so the cached app_ref is stale.

Understand the failure class

Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.

Related errors


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

Appendix: source

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

        }, timeoutMs);
      })]);
    } finally { clearTimeout(timer); }
  }

  async function recordingStart({ display, durationSec, region, app_ref, window_id } = {}) {
    const dir = recordingsDir();
    fs.mkdirSync(dir, { recursive: true });
    const id = crypto.randomBytes(4).toString("hex");
    const file = path.join(dir, `rec-${id}.mov`);
    const displays = await displayInfo();
    // app_ref scopes the recording to the app's window rect: resolved once at
    // start through the same window_info the AX path uses, so a background
    // window records behind the user's work. The rect is fixed at start —
    // it does not track later moves or resizes.
    let window = null;
    if (app_ref !== undefined || window_id != null) {
      window = await native("window_info", { app_ref: app_ref === undefined ? state.inputApp ?? undefined : app_ref, window_id });
      if (!window?.points || !(window.points.w > 0) || !(window.points.h > 0)) throw new ExecError("the selected application has no capturable window — call list_windows");
      if (region) throw new ExecError("choose app_ref or region, not both");
      region = [window.points.x, window.points.y, window.points.w, window.points.h];
    }
    let disp = display ?? state.activeDisplay;
    if (window && display == null) {
      const cx = region[0] + region[2] / 2, cy = region[1] + region[3] / 2;
      const host = displays.find(d => d.points && cx >= d.points.x && cx < d.points.x + d.points.w && cy >= d.points.y && cy < d.points.y + d.points.h);
      if (host) disp = host.index;
    }
    const selected = displays.find(d => d.index === disp);
    if (!selected) throw new ExecError("choose one available display for recording");
    if (durationSec != null && (!Number.isFinite(durationSec) || durationSec <= 0)) throw new ExecError("durationSec must be positive");
    const capabilities = await native("input_capabilities");
    if (capabilities?.record_owner_pipe !== 1) throw new ExecError("native screen recorder cannot own its client lifetime; update Computer Use before recording");
    const helper = await nativeHelper();
    throwIfAborted();
    const child = spawn(helper, [JSON.stringify({ tool: "record", args: { file, displayID: selected.id, region, durationSec, owner_pipe: true } })], { stdio: ["pipe", "pipe", "pipe"] });
    child.stdin.on("error", () => {});

View on GitHub (pinned to 73e0f67d83)