Hmbown/CodeWhale · critical · ExecError

native screen recorder cannot own its client lifetime…

Error message

native screen recorder cannot own its client lifetime; update Computer Use before recording

What it means

Before spawning the native helper recorder, the backend queries input_capabilities and requires record_owner_pipe === 1, i.e. the installed native Computer Use component supports handing recorder client-lifetime ownership through a pipe. Older helper builds lack this handshake, so recording is refused with an upgrade instruction rather than risking orphaned or mis-owned recorder processes.

Solutions

  1. Update the Computer Use native component to the version matching this backend, then retry.
  2. Verify with the helper's input_capabilities that record_owner_pipe is 1.
  3. If update is not possible, avoid app/window record on this machine or capture with screenshot instead.
  4. Check that no stale older helper binary is earlier on PATH / in the plugin's helper lookup location.
Defensive patterns

Strategy: try-catch

Try / catch

try { await record({ app_ref }) } catch (e) {
  if (String(e.message).includes("cannot own its client lifetime")) {
    // native helper is outdated; surface an upgrade instruction to the user
    console.error("Update the Computer Use native component before recording.");
  } else throw e;
}

Prevention

When it happens

Trigger: Running the darwin backend against an outdated native Computer Use helper binary whose input_capabilities reports no record_owner_pipe support; a partially updated install where the JS backend is newer than the bundled native helper.

Common situations: Upgrading the TUI/plugin but not the native helper; a stale helper cached at ~/Library or another install location; rolling back the plugin while keeping a newer/older helper.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


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

Appendix: source

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

    // 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", () => {});
    const startedAt = new Date().toISOString();
    let stderr = "", output = "", ready = false;
    const completion = new Promise(resolve => {
      child.once("error", error => resolve({ code: -1, error: error.message }));
      child.once("close", code => resolve({ code, error: stderr.trim() }));
    });
    child.stderr.on("data", chunk => { stderr = (stderr + chunk).slice(-4000); });
    const recording = { child, completion, pid: child.pid, file, startedAt, mode: "ScreenCaptureKit", display: disp };
    rec.set(id, recording);
    const signal = currentSignal();
    let timer, abort;
    try {
      await new Promise((resolve, reject) => {
        abort = () => { requestRecordingStop(recording); reject(Object.assign(new ExecError("computer request cancelled"), { code: "cancelled" })); };

View on GitHub (pinned to 73e0f67d83)