Hmbown/CodeWhale · error · ExecError

This executor cannot safely own held input; update Computer…

Error message

This executor cannot safely own held input; update Computer Use

What it means

Held input (drags, press-and-hold sequences) must be executed by an executor that can safely own a lease — i.e. the host's Computer Use runtime exposes runInputLease so held state is released if the agent disconnects. When the executor object lacks this capability, nativeLease throws instead of leaking stuck pointer state.

Solutions

  1. Update the Computer Use host/executor to a version that implements runInputLease.
  2. If you supply a custom executor, add a runInputLease(helper, argsJson) implementation that can release the lease on disconnect/crash.
  3. As a stopgap, avoid held-input tools (drags / pointer_sequence) and use tap or accessibility actions that don't require a lease.
  4. Verify the wiring: log which executor object the backend was constructed with and confirm exec.runInputLease is a function.

Example fix

// before (custom executor)
const exec = { runInput: runInput }; // no runInputLease

// after
const exec = { runInput, runInputLease }; // lease-capable executor
Defensive patterns

Strategy: try-catch

Validate before calling

if (typeof exec?.runInputLease !== 'function') {
  console.warn('executor lacks input-lease support; held-input tools unavailable');
}

Try / catch

try {
  await pointer_sequence({ actions: drag });
} catch (e) {
  if (String(e.message).includes('cannot safely own held input')) {
    // fall back to non-lease actions
    await click({ element: target });
  } else throw e;
}

Prevention

When it happens

Trigger: Calling a tool that routes through nativeLease (e.g. pointer_sequence, which also triggers requireSharedPointer) on an executor built without runInputLease — an older or minimal Computer Use host/executor implementation.

Common situations: Running the darwin backend under an outdated or custom executor that predates the input-lease API; embedding the backend in a harness that wires only runInput; plugin/host version mismatch after a Computer Use update.

Related errors


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

Appendix: source

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

      try { await updatePreview(); } catch (error) { result.preview_error = error.message; }
    }
    return result;
  }

  async function requireBackgroundActions() {
    if ((await native("input_capabilities"))?.background_actions !== 1) {
      throw Object.assign(new ExecError("Update the Computer Use helper to use background focus, selection, context menus and scrolling."), { code: "app_upgrade_required" });
    }
  }

  function assertBoundElement(target) {
    if (!state.inputApp || target.app_ref?.pid !== state.inputApp.pid) throw new ExecError("element does not belong to the bound application — open_application and observe again");
    if (!Array.isArray(target.path) || !Number.isInteger(target.windowIndex) || !target.role) throw new ExecError("element has no resolved accessibility identity");
  }

  async function nativeLease(tool, args) {
    if (tool === "pointer_sequence") requireSharedPointer();
    if (!exec.runInputLease) throw new ExecError("This executor cannot safely own held input; update Computer Use");
    if ((await native("input_capabilities"))?.input_lease !== 1) throw new ExecError("The native helper needs an update for disconnect-safe held input");
    const helper = await nativeHelper();
    try {
      return await exec.runInputLease(helper, [JSON.stringify({ tool, args: { ...args, ...yieldArgs, input_app_ref: state.inputApp, foreground_input: state.foregroundInput, owner_pipe: true, input_lease: true } })]);
    } catch (error) {
      error.code = nativeErrorCode(error.message) ?? error.code;
      throw error;
    }
  }

  async function updatePreview(show = false) {
    const win = await native("window_info", { app_ref: state.inputApp });
    const dir = path.join(stateDir(), "preview");
    fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
    const temp = path.join(dir, "next.png"), file = path.join(dir, "latest.png");
    const r = await runL("screencapture", ["-x", "-o", "-l", String(win.window_id), "-t", "png", temp], { timeoutMs: 8000 });
    if (r.code !== 0) throw new ExecError(`background preview capture failed: ${r.stderr}`);
    fs.renameSync(temp, file);

View on GitHub (pinned to 73e0f67d83)