{"record":{"id":"7720eacd74c9f1f0","repo":"stablyai/orca","slug":"click-modifiers-require-modifier-keys-only","errorCode":null,"errorMessage":"click modifiers require modifier keys only","messagePattern":"click modifiers require modifier keys only","errorType":"validation","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"native/computer-use-linux/runtime.py","lineNumber":856,"sourceCode":"    down, up = buttons[button]\n    for _ in range(parsed_count):\n        Atspi.generate_mouse_event(round(x), round(y), \"abs\")\n        Atspi.generate_mouse_event(round(x), round(y), down)\n        time.sleep(0.03)\n        Atspi.generate_mouse_event(round(x), round(y), up)\n\n\ndef click_modifier_keys(raw):\n    if raw is None:\n        return []\n    aliases = {\n        \"ctrl\": \"ctrl\", \"control\": \"ctrl\", \"cmdorctrl\": \"ctrl\", \"commandorcontrol\": \"ctrl\",\n        \"shift\": \"shift\", \"alt\": \"alt\", \"option\": \"alt\",\n        \"meta\": \"super\", \"super\": \"super\", \"win\": \"super\", \"cmd\": \"super\", \"command\": \"super\",\n    }\n    parts = [part.strip().lower() for part in str(raw).split(\"+\")]\n    if not parts or any(not part or part not in aliases for part in parts):\n        raise RuntimeError(\"click modifiers require modifier keys only\")\n    return list(dict.fromkeys(aliases[part] for part in parts))\n\n\ndef modified_click_at(x, y, button, count, modifier_keys):\n    xdotool = shutil.which(\"xdotool\")\n    is_wayland = os.environ.get(\"XDG_SESSION_TYPE\", \"\").lower() == \"wayland\"\n    if not xdotool or is_wayland:\n        raise RuntimeError(\"modified clicks require xdotool on an X11 session\")\n    button_number = {\"left\": \"1\", \"middle\": \"2\", \"right\": \"3\"}[button]\n    command = [xdotool, \"mousemove\", \"--sync\", str(round(x)), str(round(y))]\n    for modifier in modifier_keys:\n        command.extend([\"keydown\", modifier])\n    command.extend([\"click\", \"--repeat\", str(count), \"--delay\", \"35\", button_number])\n    for modifier in reversed(modifier_keys):\n        command.extend([\"keyup\", modifier])\n    try:\n        subprocess.run(command, check=True, timeout=5)\n    finally:","sourceCodeStart":838,"sourceCodeEnd":874,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/native/computer-use-linux/runtime.py#L838-L874","documentation":"Raised by click_modifier_keys (runtime.py:856) when the modifiers string, after splitting on '+', yields any empty or unrecognized part. The alias table (runtime.py:849-853) accepts ctrl/control/cmdorctrl/commandorcontrol, shift, alt/option, meta/super/win/cmd/command. Any other token — including literal '+' with nothing after, or a bare letter like 'a' — fails.","triggerScenarios":"operation['modifiers'] for the 'click' tool is set to something like 'a', 'ctrl+', 'space', or 'ctrl+shift+a'. Multi-key chords are allowed only if every segment is a recognized modifier alias.","commonSituations":"An agent treats modifiers as a free-form key list and includes a non-modifier key; a caller joins keys with '+' but accidentally includes an empty segment; confusion between this (modifiers only) and hotkey (which allows full key specs).","solutions":["Include only modifier tokens in modifiers: ctrl, shift, alt, meta/super (or their aliases).","If you need a non-modifier key with the click, send a separate press_key/hotkey action instead.","Build the modifiers string by joining only validated tokens with '+'."],"exampleFix":"// before\n{\"tool\":\"click\",\"modifiers\":\"ctrl+a\"}\n// after\n{\"tool\":\"click\",\"modifiers\":\"ctrl\"}","handlingStrategy":"validation","validationCode":"MOD_ALIASES = {\"ctrl\",\"control\",\"cmdorctrl\",\"commandorcontrol\",\"shift\",\"alt\",\"option\",\"meta\",\"super\",\"win\",\"cmd\",\"command\"}\n\ndef sanitize_modifiers(raw):\n    if not raw:\n        return None\n    parts = [p.strip().lower() for p in str(raw).split(\"+\")]\n    if all(p in MOD_ALIASES for p in parts):\n        return raw\n    return None  # drop invalid -> plain click","typeGuard":"MOD_ALIASES = {\"ctrl\",\"control\",\"cmdorctrl\",\"commandorcontrol\",\"shift\",\"alt\",\"option\",\"meta\",\"super\",\"win\",\"cmd\",\"command\"}\n\ndef is_valid_modifier_spec(raw) -> bool:\n    if not raw:\n        return True\n    parts = [p.strip().lower() for p in str(raw).split(\"+\")]\n    return bool(parts) and all(p in MOD_ALIASES for p in parts)","tryCatchPattern":"try:\n    run_operation(op)\nexcept RuntimeError as e:\n    if \"modifiers require modifier keys only\" in str(e):\n        op.pop(\"modifiers\", None)  # retry as plain click\n    else:\n        raise","preventionTips":["Include only modifier tokens (ctrl/shift/alt/meta and aliases) in modifiers.","For non-modifier keys, send a separate press_key/hotkey action.","Validate each '+'-separated token against the alias table before dispatch."],"tags":["validation","input-validation","computer-use","mouse","python"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}