{"record":{"id":"7cf77be0b1fc1959","repo":"Hmbown/CodeWhale","slug":"menu-item-not-found","errorCode":"menu_item_not_found","errorMessage":"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)","messagePattern":"menu item \"(.+?)\" not found (.+?)` : \"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\\)","errorType":"exception","errorClass":"ExecError","httpStatus":null,"severity":"error","filePath":"crates/tui/plugins/computer-use/src/backends/darwin.mjs","lineNumber":834,"sourceCode":"  }\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 });\n      pressed.push(titles[level]);\n      if (level < titles.length - 1) await wait(140);\n    }\n    return { action_sent: true, strategy: \"a11y\", route: \"accessibility\", delivery: \"background\", menu: pressed, front_lease: false,\n             note: \"Menu activation used accessibility only — no key events or focus lease. Verify the app effect (list_windows / get_app_state) before reporting success.\" };\n  }\n\n  /** Poll for the exact menu element; opens and submenu population are async. */\n  async function findMenuItem(app_ref, label, menuBar) {\n    const deadline = Date.now() + 4_000;","sourceCodeStart":816,"sourceCodeEnd":852,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/plugins/computer-use/src/backends/darwin.mjs#L816-L852","documentation":"During invokeMenu's level-by-level traversal, each title is looked up via findMenuItem against the bound app's menu bar; menus only expose their items while open, so the library presses each level and polls the next. When a title cannot be found at its level, it throws this error carrying code \"menu_item_not_found\" and reports how far the traversal got.","triggerScenarios":"A misspelled or case-mismatched menu title; omitting an ellipsis that is part of the title (e.g. \"Save As…\"); a localized menu bar; an item that only exists in a submenu reachable by a different path; the target app's menu bar not yet populated.","commonSituations":"Automating apps whose menus change by context; localizing scripts written against an English menu bar; agents guessing titles instead of reading get_app_state output.","solutions":["Call get_app_state on the bound app and copy the exact menu titles it reports (an ellipsis is part of the title).","Match case and punctuation exactly; try the trimmed title as displayed.","Split deeper navigation: if the item is beyond 3 levels or in a context submenu, navigate in steps or use an element action on the control instead.","Ensure the app is bound via open_application and its menu bar is live before invoking."],"exampleFix":"// before\nawait backend.invokeMenu([\"File\", \"Save As\"]);\n// after\nawait backend.invokeMenu([\"File\", \"Save As…\"]); // ellipsis is part of the title","handlingStrategy":"try-catch","validationCode":"null","typeGuard":null,"tryCatchPattern":"try {\n  await backend.invokeMenu(path);\n} catch (e) {\n  if (e.code === 'menu_item_not_found') {\n    const st = await backend.getAppState({});\n    // re-derive exact titles from st and retry or surface to the user\n  } else throw e;\n}","preventionTips":["Read menu titles from get_app_state instead of hardcoding them.","Include ellipses (…) exactly as the title displays them.","Expect localized menu bars — do not reuse titles across locales.","Prefer element actions on window controls when a menu path is fragile."],"tags":["menu","accessibility","macos","not-found"],"backgroundTag":"resource-not-found","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"}