Hmbown/CodeWhale · error · ExecError

Native input owner did not confirm a live input lease

Error message

Native input owner did not confirm a live input lease

What it means

runInputLease spawns a native input-owner process and waits for a handshake receipt. The lease is only accepted if the receipt carries action_sent===true AND input_lease===true; anything else (missing fields, wrong values, an unexpected first message) throws this error. It means the native owner started but never confirmed it holds a live, exclusive input lease, so the backend refuses to route input through it.

Solutions

  1. Check the native input owner's version matches the driver — upgrade or reinstall the helper binary so the handshake protocol agrees.
  2. Inspect the owner's stdout/stderr for startup errors (display/permission failures) and fix the environment it runs in.
  3. Ensure nothing else holds exclusive input (another automation session, remote-control tool) that blocks the lease.
  4. Retry; if the failure persists, capture the owner's first output line to confirm whether the handshake shape changed.

Example fix

// caller-side guard
const lease = await runInputLease(cmd, args, { timeoutMs: 20000 }).catch((e) => {
  if (String(e.message).includes('did not confirm a live input lease')) {
    return runInputLease(rebuiltCmd, [], { timeoutMs: 30000 }); // re-spawn after fixing owner
  }
  throw e;
});
Defensive patterns

Strategy: retry

Type guard

const isLeaseHandshakeError = (e) =>
  e instanceof Error && /did not confirm a live input lease|did not acknowledge input/.test(e.message);

Try / catch

try {
  lease = await runInputLease(cmd, args, { timeoutMs: 20000 });
} catch (e) {
  if (isLeaseHandshakeError(e)) {
    await killStaleOwner();
    lease = await runInputLease(cmd, args, { timeoutMs: 20000 }); // one retry
  } else throw e;
}

Prevention

When it happens

Trigger: The native input owner binary starts but its first JSON message lacks input_lease:true or action_sent:true; the owner sends a non-receipt message (log line, warning) that gets parsed as the receipt; a version mismatch between the JS driver and the native owner's handshake protocol; the owner crashes after printing a partial message.

Common situations: Out-of-date or mismatched native helper binary after an upgrade; environment (sandbox, missing display permissions) preventing the owner from acquiring input; the owner emitting startup warnings to stdout before its JSON receipt, corrupting the handshake parse.

Related errors


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

Appendix: source

Thrown at crates/tui/plugins/computer-use/src/exec.mjs:128

  const release = async (message = {}) => {
    if (!stopped) {
      stopped = true;
      if (!closed) {
        child?.stdin.end(JSON.stringify({ ...message, release: true }) + "\n");
        killTimer = setTimeout(() => {
          child?.kill("SIGTERM");
          killTimer = setTimeout(() => child?.kill("SIGKILL"), 750);
        }, 750);
      }
    }
    const result = await completion;
    clearTimeout(killTimer);
    if (result.code !== 0) throw Object.assign(new ExecError(result.stderr || "Native input cleanup failed", result), { code: result.aborted ? "cancelled" : "input_release_failed" });
  };
  let timer;
  try {
    const receipt = await Promise.race([ready, new Promise((_, reject) => { timer = setTimeout(() => reject(new ExecError("Native input owner did not acknowledge input")), opts.timeoutMs ?? 20_000); })]);
    if (receipt?.action_sent !== true || receipt?.input_lease !== true) throw new ExecError("Native input owner did not confirm a live input lease");
    return { receipt, release, async send(message) {
      const signal = currentSignal();
      let commandTimer, abort;
      try {
        throwIfAborted(signal);
        if (closed || stopped || child?.exitCode !== null || child?.signalCode) throw Object.assign(new ExecError("Native input owner is closed"), { code: "input_owner_closed" });
        const next = reply();
        const cancelled = new Promise((_, reject) => {
          abort = () => reject(Object.assign(new ExecError("computer request cancelled"), { code: "cancelled" }));
          signal?.addEventListener("abort", abort, { once: true });
        });
        child.stdin.write(JSON.stringify(message) + "\n");
        return await Promise.race([next, cancelled, new Promise((_, reject) => { commandTimer = setTimeout(() => reject(new ExecError("Native input owner did not acknowledge pointer motion")), opts.timeoutMs ?? 20_000); })]);
      }
      catch (error) { await release().catch(() => {}); throw error; }
      finally { clearTimeout(commandTimer); signal?.removeEventListener("abort", abort); }
    } };
  } catch (error) {

View on GitHub (pinned to 73e0f67d83)