Hmbown/CodeWhale · error · ExecError

open_application first — invoke_menu acts on the bound…

Error message

open_application first — invoke_menu acts on the bound application

What it means

invokeMenu acts only on the application bound by a prior open_application call; the binding lives in state.inputApp. If no application has been bound in this session, the library throws instead of guessing which app's menu bar to traverse.

Solutions

  1. Call open_application with the target app's name/bundle_id/pid before invoking menus.
  2. If a previous open_application failed, fix that error first — it cleared the binding on purpose so input cannot go to the wrong app.
  3. Use list_apps to get the exact identity if the open step is failing.

Example fix

// before
await backend.invokeMenu(["File", "New"]);
// after
await backend.openApplication({ name: "TextEdit" });
await backend.invokeMenu(["File", "New"]);
Defensive patterns

Strategy: validation

Validate before calling

if (!boundApp) await backend.openApplication({ name: targetApp });
await backend.invokeMenu(menuPath);

Try / catch

try {
  await backend.invokeMenu(path);
} catch (e) {
  if (String(e.message).startsWith('open_application first')) {
    await backend.openApplication({ name: targetApp });
    await backend.invokeMenu(path);
  } else throw e;
}

Prevention

When it happens

Trigger: Calling invoke_menu as the first computer-use action in a session, after a failed open_application (which resets state.inputApp to null), or after the binding was cleared by another failed selection call.

Common situations: Scripting menu interactions without an open step; an earlier open_application failed (e.g. app not found) and silently disarmed the binding; assuming the OS foreground app is used rather than the explicitly bound one.

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/94f07e52fba0de34. Report an issue: GitHub.

Appendix: source

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

    state.foregroundInput = !!activate;
    // Surface the watch panel on bind; a capture failure (e.g. missing Screen
    // Recording) must never block the bind itself. The first successful
    // capture also starts the refresh loop so the panel stays live while bound.
    if (state.previewEnabled) {
      previewBusy = true;
      updatePreview(true).catch(() => {}).finally(() => { previewBusy = false; });
    }
    return { launched, activate, keyboard_delivery: activate ? "foreground-guarded" : "process", input_scope: activate ? "shared-desktop" : "application", shared_pointer: !!activate, isolated_desktop: false, url: urlArg ?? null, resolved: p?.found ? { name: p.name, pid: p.pid, bundle_id: p.bundle_id, frontmost: p.frontmost } : null,
      ...(Number.isFinite(p?.yield_ms) && p.yield_ms > 0 ? { yield_ms: p.yield_ms } : {}) };
  }

  /**
   * Menu items by title path, through accessibility only: no key events, no
   * focus lease. Menus expose items only while open, so each level is pressed
   * and the next is polled for. Exact titles; an ellipsis is part of the title.
   */
  async function invokeMenu(menuPath) {
    if (!state.inputApp) throw new ExecError("open_application first — invoke_menu acts on the bound application");
    if (!Array.isArray(menuPath) || menuPath.length < 1 || menuPath.length > 3 || menuPath.some((s) => typeof s !== "string" || !s.trim())) {
      throw new ExecError('invoke_menu needs path: 1..3 non-empty menu titles, e.g. ["File","New"]');
    }
    const titles = menuPath.map((s) => s.trim());
    const app_ref = state.inputApp;
    const pressed = [];
    for (let level = 0; level < titles.length; level++) {
      const found = await findMenuItem(app_ref, titles[level], level === 0);
      if (!found) {
        throw Object.assign(new ExecError(`menu item "${titles[level]}" not found ${pressed.length ? `under ${pressed.join(" ▸ ")}` : "on the menu bar"} — menus expose items only while open; check the exact title with get_app_state (an ellipsis is part of the title)`), { code: "menu_item_not_found" });
      }
      if (found.enabled === false) {
        throw Object.assign(new ExecError(`menu item "${titles[level]}" is present but disabled right now — the app validates it against its current state (in background mode that is often a missing key window for window-targeted commands like Close). Use an element action on the window's own control instead of pressing a disabled item.`), { code: "menu_item_disabled" });
      }
      const target = { app_ref, windowIndex: found.windowIndex ?? 0, path: found.path, role: found.role, label: found.label };
      assertBoundElement(target);
      const action = found.role === "AXMenuItem" && (found.actions ?? []).includes("AXPick") ? "AXPick" : "AXPress";
      await native("perform_action", { target, action });

View on GitHub (pinned to 73e0f67d83)