Hmbown/CodeWhale · error · ExecError

element does not belong to the bound application —…

Error message

element does not belong to the bound application — open_application and observe again

What it means

The backend binds input to one application (state.inputApp) established via open_application. assertBoundElement validates that the accessibility element you are targeting was observed from that same application (matching pid). If no app is bound or the element's app_ref.pid differs from the bound pid, it throws so a stale/foreign element is never driven.

Solutions

  1. Call open_application to (re)bind the intended application, then observe() again to get fresh element handles for it.
  2. Re-run observe() after any app relaunch or rebinding — old element references are stale once the pid changes.
  3. Check the element's app_ref.pid matches the currently bound pid before passing it to an input tool.
  4. In multi-app workflows, rebind explicitly each time you switch applications instead of reusing cached targets.

Example fix

// before
const el = oldTargets["Submit"]; // observed before rebinding
await click({ element: el }); // element does not belong to the bound application

// after
await open_application("com.example.app");
const fresh = await observe();
await click({ element: fresh["Submit"] });
Defensive patterns

Strategy: validation

Validate before calling

function isUsableTarget(el, boundPid) {
  return el?.app_ref?.pid === boundPid;
}
// before acting:
// if (!isUsableTarget(el, state.boundPid)) await open_application(app);

Try / catch

try {
  await click({ element: el });
} catch (e) {
  if (String(e.message).includes('does not belong to the bound application')) {
    await open_application(el.app_ref);
    const fresh = await observe();
    await click({ element: fresh[el.label] });
  } else throw e;
}

Prevention

When it happens

Trigger: Calling an element-targeting input tool (e.g. a11y click/type) with a target whose app_ref.pid is absent or differs from state.inputApp.pid — typically after re-binding with open_application to another app, after the bound app restarted (new pid), or when passing an element from a previous observe() session.

Common situations: Automation scripts that observe app A, then open app B, then replay old element handles; app relaunch by an updater changing the pid; caching observe() results across turns; mixing targets from screenshots taken before open_application was called.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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

Appendix: source

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

      throw error;
    }
    const result = tryJson(r.stdout, null);
    const interference = leaseVerdict(result);
    if (interference !== null) result.user_input_during_lease = interference;
    if (state.previewEnabled && state.inputApp && ["type", "key_event", "pointer_sequence", "bg_pointer", "bg_key", "set_value", "select_text", "perform_action", "hit_test", "click_element", "scroll_element", "focus_element"].includes(tool)) {
      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 });

View on GitHub (pinned to 73e0f67d83)