{"record":{"id":"7ab7353b76797270","repo":"Hmbown/CodeWhale","slug":"perform-action-failed-out-code","errorCode":null,"errorMessage":"perform_action failed: ${out.code}","messagePattern":"perform_action failed: (.+?)","errorType":"exception","errorClass":"ExecError","httpStatus":null,"severity":"error","filePath":"crates/tui/plugins/computer-use/src/backends/linux.mjs","lineNumber":738,"sourceCode":"    print(json.dumps({\"ok\": True, \"after\": after}))`, String(value));\n      if (!out.ok) throw new ExecError(`set_value failed: ${out.code}`);\n      return { action_sent: true, strategy: \"a11y\", verified: true, after: out.after };\n    },\n    select_text: async () => { throw new ExecError(\"select_text is not implemented on the linux backend — fail-closed\"); },\n    perform_action: async ({ target, action }) => {\n      const body = `    a = found.queryAction()\n    names = [a.getName(i) for i in range(a.nActions)]\n    want = (extra or \"click\").lower()\n    match = next((n for n in names if n.lower() == want), None)\n    if match is None and want == \"click\":\n        match = next((n for n in names if n.lower() in (\"click\", \"press\", \"activate\")), None)\n    if match is None:\n        print(json.dumps({\"ok\": False, \"code\": \"action_not_found: \" + \",\".join(names)}))\n    else:\n        a.doAction(names.index(match))\n        print(json.dumps({\"ok\": True, \"sent\": True}))`;\n      const out = await atspiResolve(target, body, String(action));\n      if (!out.ok) throw new ExecError(`perform_action failed: ${out.code}`);\n      return { action_sent: true, strategy: \"a11y\", action };\n    },\n    read_clipboard: async () => {\n      await probeSession();\n      const cmd = session === \"x11\"\n        ? (tools.xclip ? [\"xclip\", \"-selection\", \"clipboard\", \"-o\"] : [\"xsel\", \"--clipboard\", \"--output\"])\n        : [\"wl-paste\"];\n      need(cmd[0], \"clipboard read\");\n      const r = await run(cmd[0], cmd.slice(1), { timeoutMs: 10_000 });\n      if (r.code !== 0) throw new ExecError(\"clipboard read failed\", r);\n      return { text: r.stdout, encoding: \"utf8\" };\n    },\n    write_clipboard: async ({ text }) => {\n      await probeSession();\n      const cmd = session === \"x11\"\n        ? (tools.xclip ? [\"xclip\", \"-selection\", \"clipboard\"] : [\"xsel\", \"--clipboard\", \"--input\"])\n        : [\"wl-copy\"];\n      need(cmd[0], \"clipboard write\");","sourceCodeStart":720,"sourceCodeEnd":756,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/plugins/computer-use/src/backends/linux.mjs#L720-L756","documentation":"perform_action invokes an AT-SPI action on a resolved element via an embedded python script; this error is thrown when the script returns ok=false with a code, most commonly action_not_found listing the element's available action names. The element resolved, but it does not expose the requested action.","triggerScenarios":"Requesting an action name that does not exist on the element (e.g. 'press' when the element only exposes 'click'); case/spacing mismatch in the action name; element resolves but is actionless (static text, containers); stale element path resolving to a different node that lacks the action.","commonSituations":"Hard-coding macOS-style action names against Linux a11y trees; assuming buttons expose 'press' when GTK/Qt expose 'click' or 'activate'; automating non-interactive nodes; app state changed since get_app_state so the path now points elsewhere.","solutions":["Read out.code: action_not_found includes the available names — pick one of those exact names.","Use the element's exposed action name verbatim (case-insensitive match is applied; spelling must match).","Re-resolve a fresh path via get_app_state in case the element moved or changed.","For clicks, omit the action (defaults to 'click') or pass a name present in the listed actions; otherwise drive input via left_click coordinates instead."],"exampleFix":"// before\nawait backend.perform_action({ target, action: 'press' }); // not exposed\n// after\nconst state = await backend.get_app_state({ app_ref: { name: 'Firefox' } });\nconst available = state.actions; // e.g. ['click','showContextMenu']\nawait backend.perform_action({ target, action: available.includes('click') ? 'click' : available[0] });","handlingStrategy":"try-catch","validationCode":"const state = await backend.get_app_state({ app_ref });\nconst el = findElement(state, targetPath);\nif (!el.actions?.map(a => a.toLowerCase()).includes(action.toLowerCase())) throw new Error(`action '${action}' not exposed; available: ${el.actions}`);","typeGuard":null,"tryCatchPattern":"try {\n  return await backend.perform_action({ target, action });\n} catch (e) {\n  if (String(e.message).startsWith('perform_action failed:')) {\n    const available = String(e.message).match(/action_not_found: (.*)$/)?.[1]?.split(',');\n    return backend.perform_action({ target, action: available?.[0] ?? 'click' });\n  }\n  throw e;\n}","preventionTips":["Read available action names from app state before performing an action","Use 'click' as the safe default action for buttons","Refresh element paths from a recent snapshot; stale paths resolve to wrong nodes"],"tags":["linux","accessibility","atspi","action-not-found","automation"],"backgroundTag":"action-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"}