stablyai/orca · error · RuntimeError

{action} is not a valid secondary action

Error message

{action} is not a valid secondary action

What it means

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.

Source

Thrown at native/computer-use-linux/runtime.py:1091

        if not handled:
            click_at(
                *screen_point(bounds, saved, operation.get("x"), operation.get("y"), node),
                operation.get("mouse_button", "left"),
                click_count,
                operation.get("modifiers"),
            )
            action = {"path": "synthetic", "actionName": None, "fallbackReason": "actionUnsupported"}
        else:
            labels = action_labels(node)
            action = {"path": "accessibility", "actionName": labels[preferred] if preferred is not None and preferred < len(labels) else "action", "fallbackReason": None}
    elif tool == "perform_secondary_action":
        wanted = str(operation.get("action", "")).lower()
        for index, label in enumerate(action_labels(node)):
            if label.lower() == wanted and perform_action(node, index):
                action = {"path": "accessibility", "actionName": label, "fallbackReason": None}
                break
        else:
            raise RuntimeError(f'{operation.get("action", "")} is not a valid secondary action')
    elif tool == "scroll":
        # Why: pointer wheel events should land in the requested app window even
        # when another desktop window is currently foregrounded.
        restore_window(app, window)
        scroll_at(*screen_point(bounds, saved, operation.get("x"), operation.get("y"), node), operation.get("direction"), operation.get("pages"))
        action = {"path": "synthetic", "actionName": "scroll", "fallbackReason": None}
    elif tool == "drag":
        # Why: pointer drags are synthetic global input, so activate the target
        # window before using cached coordinates from its accessibility tree.
        restore_window(app, window)
        drag_between(
            screen_point(bounds, operation.get("fromElement"), operation.get("from_x"), operation.get("from_y"), from_node),
            screen_point(bounds, operation.get("toElement"), operation.get("to_x"), operation.get("to_y"), to_node),
        )
        action = {"path": "synthetic", "actionName": "drag", "fallbackReason": None}
    elif tool == "type_text":
        type_text(require_non_empty_string(operation.get("text"), "text"))
        action = {"path": "synthetic", "actionName": "typeText", "fallbackReason": None, "verification": {"state": "unverified", "reason": "synthetic_input"}}

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Call get_app_state first and read the element's available action labels, then send one of those exact labels.
  2. Strip whitespace from operation['action'] before sending.
  3. Fall back to 'click' (the primary action) if the desired secondary action is unavailable.

Example fix

// before
{"tool":"perform_secondary_action","action":"toggle"}  // node has no 'toggle'
// after: use a label listed by get_app_state
{"tool":"perform_secondary_action","action":"press"}
Defensive patterns

Strategy: validation

Validate before calling

state = run_operation({"tool": "get_app_state", "app": app})
labels = {a.lower() for a in element_action_labels(state, element_index)}
wanted = str(raw_action).strip().lower()
if wanted not in labels:
    raise ValueError(f"action {wanted!r} not in {sorted(labels)}")
op = {"tool": "perform_secondary_action", "action": wanted}

Type guard

def is_known_action(state, element_index, action) -> bool:
    labels = {a.lower() for a in element_action_labels(state, element_index)}
    return str(action).strip().lower() in labels

Try / catch

try:
    run_operation(op)
except RuntimeError as e:
    if "is not a valid secondary action" in str(e):
        op = {"tool": "click", "element": element_index}  # fallback
        run_operation(op)
    else:
        raise

Prevention

When it happens

Trigger: 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.

Common situations: 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).

Related errors


AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12). Data as JSON: /api/errors/ff6d9f96e0b57efb. Report an issue: GitHub.