kovidgoyal/kitty · warning · ParseException

Extra characters at end of search

Error message

Extra characters at end of search

What it means

The search-query lexer returns leftover unconsumed input after tokenizing; the parser rejects it with ParseException 'Extra characters at end of search'. Usually unbalanced quotes/parens or stray characters the grammar doesn't accept.

Source

Thrown at kitty/search_query_parser.py:191

    def token_type(self) -> TokenType:
        if self.is_eof():
            return TokenType.EOF
        return self.tokens[self.current_token].type

    def is_eof(self) -> bool:
        return self.current_token >= len(self.tokens)

    def advance(self) -> None:
        self.current_token += 1

    def tokenize(self, expr: str) -> list[Token]:
        # Strip out escaped backslashes, quotes and parens so that the
        # lex scanner doesn't get confused. We put them back later.
        for k, v in replacements():
            expr = expr.replace(k, v)
        tokens, leftover = lex_scanner()(expr)
        if leftover:
            raise ParseException(_('Extra characters at end of search'))

        def unescape(x: str) -> str:
            for k, v in replacements():
                x = x.replace(v, k[1:])
            return x

        return [Token(tt, unescape(tv) if tt in (TokenType.WORD, TokenType.QUOTED_WORD) else tv) for tt, tv in tokens]

    def parse(self, expr: str, locations: Sequence[str]) -> SearchTreeNode:
        self.locations = locations
        self.tokens = self.tokenize(expr)
        self.current_token = 0
        prog = self.or_expression()
        if not self.is_eof():
            raise ParseException(_('Extra characters at end of search'))
        return prog

    def or_expression(self) -> SearchTreeNode:

View on GitHub (pinned to 6d5d0c4406)

Solutions

  1. Balance all quotes and parentheses in the search expression
  2. Escape special characters (backslash, quotes, parens) as the parser expects
  3. Trim trailing junk/whitespace-specials before submitting

Example fix

# before
parse_query('"unclosed')
# after
parse_query('"unclosed"')
Defensive patterns

Strategy: validation

Validate before calling

def balanced(q):
    return q.count('"')%2==0 and q.count('(')==q.count(')')
assert balanced(query)

Type guard

def is_balanced_query(q: str) -> bool: return q.count('"')%2==0 and q.count('(')==q.count(')')

Try / catch

except ParseException: show user a syntax hint and re-prompt

Prevention

When it happens

Trigger: Calling search with an expression like '"unclosed' or 'foo )' where escaped-unescape round-tripping leaves trailing tokens the scanner could not consume.

Common situations: Programmatically building search queries without escaping quotes/parens, or user-typed queries with mismatched delimiters.

Related errors


AI-assisted analysis of kovidgoyal/kitty@6d5d0c4406 (2026-08-27). Data as JSON: /api/errors/e796f21bc5a529c1. Report an issue: GitHub.