{"record":{"id":"399105e7f9bfbced","repo":"kovidgoyal/kitty","slug":"failed-to-parse-query-r-too-much-recursion-requ","errorCode":null,"errorMessage":"Failed to parse {query!r}, too much recursion required","messagePattern":"Failed to parse (.+?), too much recursion required","errorType":"validation","errorClass":"ParseException","httpStatus":null,"severity":"error","filePath":"kitty/search_query_parser.py","lineNumber":290,"sourceCode":"                tt = self.token(advance=True)\n                assert tt is not None\n                return TokenNode(loc, tt)\n            return TokenNode(loc.lower(), ':'.join(words))\n\n        if self.allow_no_location:\n            return TokenNode('all', ':'.join(words))\n        raise NoLocation(tt)\n\n\n@lru_cache(maxsize=64)\ndef build_tree(query: str, locations: str | tuple[str, ...], allow_no_location: bool = False) -> SearchTreeNode:\n    if isinstance(locations, str):\n        locations = tuple(locations.split())\n    p = Parser(allow_no_location)\n    try:\n        return p.parse(query, locations)\n    except RuntimeError as e:\n        raise ParseException(f'Failed to parse {query!r}, too much recursion required') from e\n\n\ndef search(\n    query: str,\n    locations: str | tuple[str, ...],\n    universal_set: set[T],\n    get_matches: GetMatches[T],\n    allow_no_location: bool = False,\n) -> set[T]:\n    return build_tree(query, locations, allow_no_location).search(universal_set, get_matches)\n","sourceCodeStart":272,"sourceCodeEnd":301,"githubUrl":"https://github.com/kovidgoyal/kitty/blob/6d5d0c440603ad9bdf6dcd599f73f6dde21acb44/kitty/search_query_parser.py#L272-L301","documentation":"Raised when the recursive-descent parser blows Python's recursion limit while parsing a search query, i.e. the expression is too deeply nested or too long. The underlying RecursionError from parse() is caught in build_tree and re-raised as a ParseException with this message.","triggerScenarios":"Queries with very deep parenthesis nesting (hundreds of levels), extremely long chains of 'and'/'or' terms, or machine-generated queries of pathological length passed to kitty.search.query_parser.search()/build_tree().","commonSituations":"Programmatically generated queries from large term lists; adversarial/fuzzed input fed to the parser; nesting from auto-wrapping logic that adds parens per iteration.","solutions":["Flatten the query: use a flat chain of 'and'/'or' instead of nested parentheses","Limit or batch the terms you feed into one query","Catch ParseException at the call site and reject/split the query","If legitimately deep nesting is required, raise sys.setrecursionlimit cautiously — better to restructure the query"],"exampleFix":"# before\nq = '(' * 5000 + 'foo' + ')' * 5000\nbuild_tree(q, locations)\n# after\nq = ' and '.join(terms)  # flat conjunction\nbuild_tree(q, locations)","handlingStrategy":"try-catch","validationCode":"def depth_ok(q: str, limit: int = 200) -> bool:\n    depth = mx = 0\n    for ch in q:\n        if ch == '(':\n            depth += 1\n            mx = max(mx, depth)\n        elif ch == ')':\n            depth -= 1\n    return mx <= limit and len(q) < 100_000","typeGuard":null,"tryCatchPattern":"from kitty.search_query_parser import ParseException\ntry:\n    node = build_tree(query, locations)\nexcept ParseException as e:\n    if 'too much recursion' in str(e):\n        query = flatten_query(query)  # or split into batches\n    else:\n        raise","preventionTips":["Prefer flat and/or chains over nesting","Cap query length and nesting depth at generation time","Catch ParseException and degrade gracefully"],"tags":["kitty","search","recursion","resource-limit"],"backgroundTag":"recursion-depth-exceeded","analyzedSha":"6d5d0c440603ad9bdf6dcd599f73f6dde21acb44","analyzedAt":"2026-08-27T14:20:20.142Z","schemaVersion":2},"datasetVersion":"2026-08-27T19:17:21.184Z"}