{"record":{"id":"94f07e52fba0de34","repo":"Hmbown/CodeWhale","slug":"open-application-first-invoke-menu-acts-on-the-bound","errorCode":null,"errorMessage":"open_application first — invoke_menu acts on the bound application","messagePattern":"open_application first — invoke_menu acts on the bound application","errorType":"exception","errorClass":"ExecError","httpStatus":null,"severity":"error","filePath":"crates/tui/plugins/computer-use/src/backends/darwin.mjs","lineNumber":824,"sourceCode":"    state.foregroundInput = !!activate;\n    // Surface the watch panel on bind; a capture failure (e.g. missing Screen\n    // Recording) must never block the bind itself. The first successful\n    // capture also starts the refresh loop so the panel stays live while bound.\n    if (state.previewEnabled) {\n      previewBusy = true;\n      updatePreview(true).catch(() => {}).finally(() => { previewBusy = false; });\n    }\n    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,\n      ...(Number.isFinite(p?.yield_ms) && p.yield_ms > 0 ? { yield_ms: p.yield_ms } : {}) };\n  }\n\n  /**\n   * Menu items by title path, through accessibility only: no key events, no\n   * focus lease. Menus expose items only while open, so each level is pressed\n   * and the next is polled for. Exact titles; an ellipsis is part of the title.\n   */\n  async function invokeMenu(menuPath) {\n    if (!state.inputApp) throw new ExecError(\"open_application first — invoke_menu acts on the bound application\");\n    if (!Array.isArray(menuPath) || menuPath.length < 1 || menuPath.length > 3 || menuPath.some((s) => typeof s !== \"string\" || !s.trim())) {\n      throw new ExecError('invoke_menu needs path: 1..3 non-empty menu titles, e.g. [\"File\",\"New\"]');\n    }\n    const titles = menuPath.map((s) => s.trim());\n    const app_ref = state.inputApp;\n    const pressed = [];\n    for (let level = 0; level < titles.length; level++) {\n      const found = await findMenuItem(app_ref, titles[level], level === 0);\n      if (!found) {\n        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\" });\n      }\n      if (found.enabled === false) {\n        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\" });\n      }\n      const target = { app_ref, windowIndex: found.windowIndex ?? 0, path: found.path, role: found.role, label: found.label };\n      assertBoundElement(target);\n      const action = found.role === \"AXMenuItem\" && (found.actions ?? []).includes(\"AXPick\") ? \"AXPick\" : \"AXPress\";\n      await native(\"perform_action\", { target, action });","sourceCodeStart":806,"sourceCodeEnd":842,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/plugins/computer-use/src/backends/darwin.mjs#L806-L842","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Call open_application with the target app's name/bundle_id/pid before invoking menus.","If a previous open_application failed, fix that error first — it cleared the binding on purpose so input cannot go to the wrong app.","Use list_apps to get the exact identity if the open step is failing."],"exampleFix":"// before\nawait backend.invokeMenu([\"File\", \"New\"]);\n// after\nawait backend.openApplication({ name: \"TextEdit\" });\nawait backend.invokeMenu([\"File\", \"New\"]);","handlingStrategy":"validation","validationCode":"if (!boundApp) await backend.openApplication({ name: targetApp });\nawait backend.invokeMenu(menuPath);","typeGuard":null,"tryCatchPattern":"try {\n  await backend.invokeMenu(path);\n} catch (e) {\n  if (String(e.message).startsWith('open_application first')) {\n    await backend.openApplication({ name: targetApp });\n    await backend.invokeMenu(path);\n  } else throw e;\n}","preventionTips":["Treat open_application as a required precondition for every input/menu action.","Remember a failed open_application clears the previous binding — re-open after failures.","Sequence actions explicitly: open → interact; never assume OS foreground equals binding."],"tags":["stateful-api","call-order","macos"],"backgroundTag":"invalid-state-transition","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-22T16:17:23.217Z"}