{"record":{"id":"ff6d9f96e0b57efb","repo":"stablyai/orca","slug":"action-is-not-a-valid-secondary-action","errorCode":null,"errorMessage":"{action} is not a valid secondary action","messagePattern":"(.+?) is not a valid secondary action","errorType":"validation","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"native/computer-use-linux/runtime.py","lineNumber":1091,"sourceCode":"        if not handled:\n            click_at(\n                *screen_point(bounds, saved, operation.get(\"x\"), operation.get(\"y\"), node),\n                operation.get(\"mouse_button\", \"left\"),\n                click_count,\n                operation.get(\"modifiers\"),\n            )\n            action = {\"path\": \"synthetic\", \"actionName\": None, \"fallbackReason\": \"actionUnsupported\"}\n        else:\n            labels = action_labels(node)\n            action = {\"path\": \"accessibility\", \"actionName\": labels[preferred] if preferred is not None and preferred < len(labels) else \"action\", \"fallbackReason\": None}\n    elif tool == \"perform_secondary_action\":\n        wanted = str(operation.get(\"action\", \"\")).lower()\n        for index, label in enumerate(action_labels(node)):\n            if label.lower() == wanted and perform_action(node, index):\n                action = {\"path\": \"accessibility\", \"actionName\": label, \"fallbackReason\": None}\n                break\n        else:\n            raise RuntimeError(f'{operation.get(\"action\", \"\")} is not a valid secondary action')\n    elif tool == \"scroll\":\n        # Why: pointer wheel events should land in the requested app window even\n        # when another desktop window is currently foregrounded.\n        restore_window(app, window)\n        scroll_at(*screen_point(bounds, saved, operation.get(\"x\"), operation.get(\"y\"), node), operation.get(\"direction\"), operation.get(\"pages\"))\n        action = {\"path\": \"synthetic\", \"actionName\": \"scroll\", \"fallbackReason\": None}\n    elif tool == \"drag\":\n        # Why: pointer drags are synthetic global input, so activate the target\n        # window before using cached coordinates from its accessibility tree.\n        restore_window(app, window)\n        drag_between(\n            screen_point(bounds, operation.get(\"fromElement\"), operation.get(\"from_x\"), operation.get(\"from_y\"), from_node),\n            screen_point(bounds, operation.get(\"toElement\"), operation.get(\"to_x\"), operation.get(\"to_y\"), to_node),\n        )\n        action = {\"path\": \"synthetic\", \"actionName\": \"drag\", \"fallbackReason\": None}\n    elif tool == \"type_text\":\n        type_text(require_non_empty_string(operation.get(\"text\"), \"text\"))\n        action = {\"path\": \"synthetic\", \"actionName\": \"typeText\", \"fallbackReason\": None, \"verification\": {\"state\": \"unverified\", \"reason\": \"synthetic_input\"}}","sourceCodeStart":1073,"sourceCodeEnd":1109,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/native/computer-use-linux/runtime.py#L1073-L1109","documentation":"Raised in the perform_secondary_action branch of run_operation (runtime.py:1091) when no accessibility action label on the target node case-insensitively equals operation['action'], or when the matching label's perform_action call returned False (the for/else ensures the raise runs only if no break occurred — i.e. no label both matched and executed successfully). The placeholder is operation.get('action','') verbatim.","triggerScenarios":"Calling the 'perform_secondary_action' tool with operation['action'] set to a label not exposed by the element (e.g. 'toggle' on a node that only exposes 'click','press'); or a label that matches but perform_action fails (do_action returns falsy), leaving the loop without a break.","commonSituations":"An agent guesses an action name without first reading get-app-state's action list for the element; UI changed and a previously valid action label disappeared; case mismatch (handled, but trailing whitespace is not stripped).","solutions":["Call get_app_state first and read the element's available action labels, then send one of those exact labels.","Strip whitespace from operation['action'] before sending.","Fall back to 'click' (the primary action) if the desired secondary action is unavailable."],"exampleFix":"// before\n{\"tool\":\"perform_secondary_action\",\"action\":\"toggle\"}  // node has no 'toggle'\n// after: use a label listed by get_app_state\n{\"tool\":\"perform_secondary_action\",\"action\":\"press\"}","handlingStrategy":"validation","validationCode":"state = run_operation({\"tool\": \"get_app_state\", \"app\": app})\nlabels = {a.lower() for a in element_action_labels(state, element_index)}\nwanted = str(raw_action).strip().lower()\nif wanted not in labels:\n    raise ValueError(f\"action {wanted!r} not in {sorted(labels)}\")\nop = {\"tool\": \"perform_secondary_action\", \"action\": wanted}","typeGuard":"def is_known_action(state, element_index, action) -> bool:\n    labels = {a.lower() for a in element_action_labels(state, element_index)}\n    return str(action).strip().lower() in labels","tryCatchPattern":"try:\n    run_operation(op)\nexcept RuntimeError as e:\n    if \"is not a valid secondary action\" in str(e):\n        op = {\"tool\": \"click\", \"element\": element_index}  # fallback\n        run_operation(op)\n    else:\n        raise","preventionTips":["Always call get_app_state and read the element's action labels before issuing perform_secondary_action.","Strip whitespace from the action name before sending.","Fall back to the primary click action when the desired secondary action is absent."],"tags":["computer-use","accessibility","atspi","input-validation","python"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}