stablyai/orca · warning · RuntimeError

stale element frame; run get-app-state again and use a fresh

Error message

stale element frame; run get-app-state again and use a fresh element index

What it means

Raised by screen_point (runtime.py:791-796) when saved_element is not None (the caller passed an element index from a prior snapshot) but the live AT-SPI node's screen_rect returned None — meaning the element no longer has a visible extent (was scrolled off-screen, removed, hidden, or its window closed). Because the caller provided an element reference rather than explicit x/y, there is no coordinate fallback, so the bridge tells the caller the cached element is stale and to refresh.

Source

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

    for index in range(int(attempt(node.get_n_actions, 0) or 0)):
        label = str(attempt(lambda i=index: node.get_action_name(i), "") or "").lower()
        if label in priority:
            return index
        if fallback is None and any(term in label for term in ("click", "press", "activate")):
            fallback = index
    return fallback


def perform_action(node, index):
    return bool(index is not None and attempt(lambda: node.do_action(int(index)), False))


def screen_point(window_rect, saved_element=None, x=None, y=None, node=None):
    rect = screen_rect(node) if node is not None else None
    if rect is not None:
        return rect.x + rect.width / 2, rect.y + rect.height / 2
    if saved_element is not None:
        raise RuntimeError("stale element frame; run get-app-state again and use a fresh element index")
    if window_rect is None or x is None or y is None:
        raise RuntimeError("coordinate action requires a visible window and coordinates")
    return window_rect.x + float(x), window_rect.y + float(y)


def require_positive_integer(value, name):
    try:
        parsed = int(value)
    except (TypeError, ValueError):
        raise RuntimeError(f"{name} must be a positive integer")
    if parsed <= 0:
        raise RuntimeError(f"{name} must be a positive integer")
    return parsed


def require_positive_number(value, name):
    try:
        parsed = float(value)

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Call get-app-state again to obtain a fresh snapshot and use a new element index.
  2. Target by explicit x/y coordinates (operation.x/y) instead of an element handle when the UI is volatile.
  3. After UI-changing actions (clicks that navigate, scrolls), refresh the snapshot before referencing element indices.
  4. Reduce the time between snapshot and action to minimize reflow windows.

Example fix

// before — reusing a stale element index after UI changed
{ "app": "Browser", "element": 42, "tool": "click" }  // element 42 no longer has a rect

// after — refresh snapshot, then target fresh index or coordinates
// 1. call get-app-state
// 2. find the element's new index (or its current x/y)
{ "app": "Browser", "x": 320, "y": 480, "tool": "click" }
Defensive patterns

Strategy: retry

Validate before calling

# Before acting on an element, verify it still has a rect
node = find_element(app, saved_element)
if node is not None and screen_rect(node) is None:
    # element is stale — refresh snapshot before action
    snapshot = make_snapshot(query, include_screenshot=False)
    # re-resolve the element index from the fresh snapshot

Type guard

def element_has_live_rect(app, saved_element) -> bool:
    node = find_element(app, saved_element)
    return node is not None and screen_rect(node) is not None

Try / catch

try:
    point = screen_point(bounds, saved_element, x, y, node)
except RuntimeError as exc:
    if 'stale element frame' in str(exc):
        snapshot = make_snapshot(query, include_screenshot=False)
        # re-acquire element index or fall back to explicit x/y from the fresh snapshot
        point = screen_point(bounds, None, fresh_x, fresh_y, None)
    else:
        raise

Prevention

When it happens

Trigger: click/scroll/drag operation referenced operation.get('element') (a saved element handle) whose underlying AT-SPI node now returns no extent: UI reflowed (list scrolled, dialog dismissed, view switched), element recycled/removed, or window resized so the element is off-screen. screen_rect returns None when get_component_iface is null or width/height ≤ 0.

Common situations: Agent cached an element index from a get-app-state snapshot, performed intermediate actions that reflowed the UI, then tried to act on the stale element. Web apps with dynamic lists, virtualized scrollers, and route changes are common culprits; native apps with collapsing sidebars/animations also trigger it.

Related errors


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