{"record":{"id":"d129d8e3219e3532","repo":"kovidgoyal/kitty","slug":"invalid-syntax-expected-a-lookup-name-or-a-word","errorCode":null,"errorMessage":"Invalid syntax. Expected a lookup name or a word","messagePattern":"Invalid syntax\\. Expected a lookup name or a word","errorType":"validation","errorClass":"ParseException","httpStatus":null,"severity":"error","filePath":"kitty/search_query_parser.py","lineNumber":241,"sourceCode":"        if (self.token_type() in (TokenType.WORD, TokenType.QUOTED_WORD) or self.token() == '(') and self.lcase_token() != 'or':\n            return AndNode(lhs, self.and_expression())\n        return lhs\n\n    def not_expression(self) -> SearchTreeNode:\n        if self.lcase_token() == 'not':\n            self.advance()\n            return NotNode(self.not_expression())\n        return self.location_expression()\n\n    def location_expression(self) -> SearchTreeNode:\n        if self.token_type() == TokenType.OPCODE and self.token() == '(':\n            self.advance()\n            res = self.or_expression()\n            if self.token_type() != TokenType.OPCODE or self.token(advance=True) != ')':\n                raise ParseException(_('missing )'))\n            return res\n        if self.token_type() not in (TokenType.WORD, TokenType.QUOTED_WORD):\n            raise ParseException(_('Invalid syntax. Expected a lookup name or a word'))\n\n        return self.base_token()\n\n    def base_token(self) -> SearchTreeNode:\n        if self.token_type() is TokenType.QUOTED_WORD:\n            tt = self.token(advance=True)\n            assert tt is not None\n            if self.allow_no_location:\n                return TokenNode('all', tt)\n            raise NoLocation(tt)\n\n        tt = self.token(advance=True)\n        assert tt is not None\n        words = tt.split(':')\n        # The complexity here comes from having colon-separated search\n        # values. That forces us to check that the first \"word\" in a colon-\n        # separated group is a valid location. If not, then the token must\n        # be reconstructed. We also have the problem that locations can be","sourceCodeStart":223,"sourceCodeEnd":259,"githubUrl":"https://github.com/kovidgoyal/kitty/blob/6d5d0c440603ad9bdf6dcd599f73f6dde21acb44/kitty/search_query_parser.py#L223-L259","documentation":"Raised when the parser reaches a position where a word, quoted word, or '(' was expected but finds something else (an operator like 'and'/'or'/'not' in the wrong place, or a ')' too early). It is the grammar's 'expected a terminal' error in location_expression.","triggerScenarios":"Queries like 'and foo' (leading operator), 'foo and or bar' (two consecutive operators), 'not and foo', or '()' (empty parentheses).","commonSituations":"Typos in search queries; concatenating optional filter fragments such that an 'and' is left dangling when one side is empty (e.g. f'{term} and {optional_filter}' with optional_filter='').","solutions":["Remove or fix dangling operators: never start a query with 'and'/'or', never put two operators in a row","When concatenating optional fragments, filter out empty parts first: ' and '.join(x for x in parts if x)","Avoid empty parentheses '()'"],"exampleFix":"# before\nparts = [term, optional_filter]\nq = ' and '.join(parts)  # optional_filter may be ''\n# after\nq = ' and '.join(p for p in [term, optional_filter] if p)","handlingStrategy":"validation","validationCode":"import re\n\ndef wellformed(q: str) -> bool:\n    # reject empty groups and dangling operators\n    if '()' in q or re.search(r'(^|\\s)(and|or|not)(\\s|$)*$', q) or re.search(r'^(and|or)(\\s|$)', q):\n        return False\n    return not re.search(r'\\b(and|or|not)\\s+(and|or|not)\\b', q)","typeGuard":null,"tryCatchPattern":"try:\n    node = build_tree(query, locations)\nexcept ParseException as e:\n    show_error(f'Invalid query syntax: {e}')","preventionTips":["Filter empty fragments before joining with and/or","Never place two operators adjacently","Dry-run generated queries through search()"],"tags":["kitty","search","query-parser","syntax-error"],"backgroundTag":"query-parse-error","analyzedSha":"6d5d0c440603ad9bdf6dcd599f73f6dde21acb44","analyzedAt":"2026-08-27T14:20:20.142Z","schemaVersion":2},"datasetVersion":"2026-08-27T19:17:21.184Z"}