{"record":{"id":"c152b119cdeec05f","repo":"kovidgoyal/kitty","slug":"missing","errorCode":null,"errorMessage":"missing )","messagePattern":"missing \\)","errorType":"validation","errorClass":"ParseException","httpStatus":null,"severity":"error","filePath":"kitty/search_query_parser.py","lineNumber":238,"sourceCode":"            return AndNode(lhs, self.and_expression())\n\n        # Account for the optional 'and'\n        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","sourceCodeStart":220,"sourceCodeEnd":256,"githubUrl":"https://github.com/kovidgoyal/kitty/blob/6d5d0c440603ad9bdf6dcd599f73f6dde21acb44/kitty/search_query_parser.py#L220-L256","documentation":"Raised when a '(' was consumed in a search query's location_expression but the matching ')' is not the next token after the inner expression is parsed. The grammar requires every parenthesized group to be explicitly closed. The error propagates up through not_expression to the top-level parse.","triggerScenarios":"Queries like '(foo or bar' (missing close paren), '(foo and (bar' (nested unbalanced), or '(foo ) extra' where the token after the inner expression is not ')'.","commonSituations":"Dynamically building queries by concatenating fragments and losing a closing parenthesis; hand-written complex queries in kitty's search UI or in templates generating search strings.","solutions":["Balance the parentheses: add the missing ')' e.g. '(foo or bar)'","For programmatically built queries, count parens or build the query from a small helper that wraps groups automatically","Test generated queries with kitty.search.query_parser.search() before using them"],"exampleFix":"# before\nsearch('(title:foo or title:bar', ...)\n# after\nsearch('(title:foo or title:bar)', ...)","handlingStrategy":"validation","validationCode":"def balanced(q: str) -> bool:\n    depth = 0\n    for ch in q:\n        if ch == '(':\n            depth += 1\n        elif ch == ')':\n            depth -= 1\n            if depth < 0:\n                return False\n    return depth == 0","typeGuard":null,"tryCatchPattern":"try:\n    node = build_tree(query, locations)\nexcept ParseException as e:\n    if 'missing )' in str(e):\n        query = query + ')' * query.count('(') - query.count(')') if False else query  # inspect manually\n    raise","preventionTips":["Paren-balance check before parsing","Build nested queries with a helper that opens and closes parens together","Never hand-edit half of a parenthesized group"],"tags":["kitty","search","query-parser","unbalanced-parentheses"],"backgroundTag":"query-parse-error","analyzedSha":"6d5d0c440603ad9bdf6dcd599f73f6dde21acb44","analyzedAt":"2026-08-27T14:20:20.142Z","schemaVersion":2},"datasetVersion":"2026-08-27T19:17:21.184Z"}