stablyai/orca · error · RuntimeError

direction is required

Error message

direction is required

What it means

Raised by scroll_at (runtime.py:884) when direction is None or strips to an empty string. Direction is a required field for the scroll tool because the wheel-event mapping (up/down/left/right) depends on it. This fires before the direction is lowercased and looked up.

Solutions

  1. Always set 'direction' to one of 'up', 'down', 'left', 'right' for scroll operations.
  2. If your controller has a default scroll direction, populate the field explicitly before dispatch.
  3. Reject empty direction in your own input layer rather than relying on the runtime.

Example fix

// before
{"tool":"scroll","pages":2}
// after
{"tool":"scroll","direction":"down","pages":2}
Defensive patterns

Strategy: validation

Validate before calling

DIRECTIONS = {"up", "down", "left", "right"}
direction = (raw_direction or "").strip().lower()
if direction not in DIRECTIONS:
    direction = "down"  # sane default
op = {"tool": "scroll", "direction": direction}

Type guard

def is_scroll_direction(value) -> bool:
    return (value or "").strip().lower() in {"up", "down", "left", "right"}

Try / catch

try:
    run_operation(op)
except RuntimeError as e:
    if "direction is required" in str(e):
        op["direction"] = "down"
    else:
        raise

Prevention

When it happens

Trigger: Calling the 'scroll' tool with operation['direction'] missing, null, or whitespace-only (' '). An empty string after .strip() is treated as absent.

Common situations: An agent omits direction assuming a default; a caller forwards a templated field that resolved to empty; whitespace from a malformed JSON value.

Related errors


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

Appendix: source

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

    command = [xdotool, "mousemove", "--sync", str(round(x)), str(round(y))]
    for modifier in modifier_keys:
        command.extend(["keydown", modifier])
    command.extend(["click", "--repeat", str(count), "--delay", "35", button_number])
    for modifier in reversed(modifier_keys):
        command.extend(["keyup", modifier])
    try:
        subprocess.run(command, check=True, timeout=5)
    finally:
        subprocess.run(
            [xdotool, *[item for modifier in reversed(modifier_keys) for item in ("keyup", modifier)]],
            check=False,
            timeout=2,
        )


def scroll_at(x, y, direction, pages):
    if direction is None or str(direction).strip() == "":
        raise RuntimeError("direction is required")
    direction = str(direction).lower()
    wheel_events = {
        "up": ("b4p", "b4r"),
        "down": ("b5p", "b5r"),
        "left": ("b6p", "b6r"),
        "right": ("b7p", "b7r"),
    }
    if direction not in wheel_events:
        raise RuntimeError(f"unsupported scroll direction: {direction}")
    down, up = wheel_events[direction]
    page_count = max(1, math.ceil(require_positive_number(1 if pages is None else pages, "pages")))
    for _ in range(page_count):
        Atspi.generate_mouse_event(round(x), round(y), "abs")
        Atspi.generate_mouse_event(round(x), round(y), down)
        time.sleep(0.03)
        Atspi.generate_mouse_event(round(x), round(y), up)

View on GitHub (pinned to 1136503c6a)