{"record":{"id":"ad882821a9e4a0dc","repo":"kovidgoyal/kitty","slug":"the-menu-entry-val-must-have-an-action","errorCode":null,"errorMessage":"The menu entry {val} must have an action","messagePattern":"The menu entry (.+?) must have an action","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"kitty/options/utils.py","lineNumber":1161,"sourceCode":"        yield val, val\n\n\ndef menu_map(val: str, current_val: Container[str]) -> Iterable[tuple[tuple[str, ...], str]]:\n    parts = val.split(maxsplit=1)\n    if len(parts) != 2:\n        raise ValueError(f'Ignoring invalid menu action: {val}')\n    if parts[0] != 'global':\n        raise ValueError(f'Unknown menu type: {parts[0]}. Known types: global')\n    start = 0\n    if parts[1].startswith('\"'):\n        start = 1\n        idx = parts[1].find('\"', 1)\n        if idx == -1:\n            raise ValueError(f'The menu entry name in {val} must end with a double quote')\n    else:\n        idx = parts[1].find(' ')\n        if idx == -1:\n            raise ValueError(f'The menu entry {val} must have an action')\n    location = ('global',) + tuple(parts[1][start:idx].split('::'))\n    yield location, parts[1][idx + 1 :].lstrip()\n\n\nallowed_shell_integration_values = frozenset({'enabled', 'disabled', 'no-rc', 'no-cursor', 'no-title', 'no-prompt-mark', 'no-complete', 'no-cwd', 'no-sudo'})\n\n\ndef shell_integration(x: str) -> frozenset[str]:\n    q = frozenset(x.lower().split())\n    if not q.issubset(allowed_shell_integration_values):\n        log_error(f'Invalid shell integration options: {q - allowed_shell_integration_values}, ignoring')\n        return q & allowed_shell_integration_values or frozenset({'invalid'})\n    return q\n\n\ndef confirm_close(x: str) -> tuple[int, bool]:\n    parts = x.split(maxsplit=1)\n    num = int(parts[0])","sourceCodeStart":1143,"sourceCodeEnd":1179,"githubUrl":"https://github.com/kovidgoyal/kitty/blob/6d5d0c440603ad9bdf6dcd599f73f6dde21acb44/kitty/options/utils.py#L1143-L1179","documentation":"Raised by kitty's menu action parser when a menu entry value (e.g. a 'global' menu specification) has no action part after the location. The parser splits the value after the location token and searches for a space to separate the menu path from the action; if no space/action is found the value is considered malformed.","triggerScenarios":"Setting a menu-related option like `map ... global::Menu/Item` variant entries where the string after parsing contains a quoted location but no trailing action text, e.g. 'global \"File::Open\"' with nothing after it, or an unquoted location with no space-delimited action.","commonSituations":"Typos in kitten/config menu definitions, forgetting the action argument at the end of a menu entry, or copying a partial example from docs.","solutions":["Add the action after the menu path, separated by whitespace","Quote the menu name correctly if it contains spaces and still append an action","Check kitty docs for the menu entry syntax for the option you are configuring"],"exampleFix":"# before\nmenu_entry 'global \"File::Open\"'\n# after\nmenu_entry 'global \"File::Open\" :: open_file'","handlingStrategy":"validation","validationCode":"import re\ndef valid_menu_entry(v: str) -> bool:\n    return bool(re.search(r'\\s', v.strip().split(maxsplit=1)[-1]))","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always end menu entries with an action token","Validate config with `kitty --debug-config` before reloading"],"tags":["kitty","config","menu","parse-error"],"backgroundTag":"config-value-parse-failed","analyzedSha":"6d5d0c440603ad9bdf6dcd599f73f6dde21acb44","analyzedAt":"2026-08-27T14:20:20.142Z","schemaVersion":2},"datasetVersion":"2026-08-27T19:17:21.184Z"}