Hmbown/CodeWhale · error · ExecError

The native helper needs an update for disconnect-safe held…

Error message

The native helper needs an update for disconnect-safe held input

What it means

Even when the executor supports leases, the compiled native helper itself must advertise `input_lease: 1` in its input_capabilities for disconnect-safe held input. nativeLease queries the helper's capabilities and throws if the capability flag is missing, because older helpers would leak held pointer/keyboard state on disconnect.

Solutions

  1. Delete the stale cached helper: rm ~/.codewhale-cu/bin/accessibility-* so the backend recompiles from current source.
  2. Update Computer Use so a fresh helper with input_lease support is built/bundled.
  3. Verify capabilities manually by running the helper's input_capabilities and checking input_lease === 1.
  4. Avoid held-input tools until the helper is updated; use tap/accessibility actions instead.

Example fix

// before (shell)
# old cached helper reused
pointer_sequence({ hold: ... }) // helper needs an update

// after (shell, one-time)
rm -f ~/.codewhale-cu/bin/accessibility-*
pointer_sequence({ hold: ... })
Defensive patterns

Strategy: validation

Validate before calling

const caps = await native('input_capabilities');
if (caps?.input_lease !== 1) {
  throw new Error('refresh helper cache: rm ~/.codewhale-cu/bin/accessibility-*');
}

Try / catch

try {
  await pointer_sequence(args);
} catch (e) {
  if (String(e.message).includes('needs an update for disconnect-safe held input')) {
    throw new Error('delete cached native helper (~/.codewhale-cu/bin) and restart to rebuild');
  }
  throw e;
}

Prevention

When it happens

Trigger: nativeLease runs `native("input_capabilities")` and the returned object's input_lease is not 1 — the cached native helper at ~/.codewhale-cu/bin/accessibility-<hash> was compiled from an older helper source and is being reused after the backend was updated.

Common situations: A stale cached helper compiled before the input-lease feature was added; the app bundles an outdated helper so the clang-rebuild path (which would compile fresh source) is skipped; failed cache invalidation after a Computer Use upgrade.

Related errors


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

Appendix: source

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

    }
    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);
    const p = state.pointer;

View on GitHub (pinned to 73e0f67d83)