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
- Check the native input owner's version matches the driver — upgrade or reinstall the helper binary so the handshake protocol agrees.
- Inspect the owner's stdout/stderr for startup errors (display/permission failures) and fix the environment it runs in.
- Ensure nothing else holds exclusive input (another automation session, remote-control tool) that blocks the lease.
- 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
- Keep the native input-owner binary version-locked with the JS driver.
- Capture and log the owner's stdout to detect protocol or startup changes early.
- Ensure no other tool holds exclusive input on the session.
- Verify display/input permissions in sandboxed or service contexts before spawning the owner.
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
- Automation name is required
- Automation prompt is required
- Cannot resolve current binary path
- Cloud agent harness exited with code
- exited
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)