oraios/serena · error

Invalid glob brace expression in {pattern!r}: unmatched brac

Error message

Invalid glob brace expression in {pattern!r}: unmatched brace

What it means

_expand_braces loops expanding innermost brace groups while a {...} matches; if braces remain unbalanced (an opening brace with no matching close), the regex finds nothing and it raises ValueError 'unmatched brace'. This prevents silently treating malformed globs as literal patterns.

Source

Thrown at src/serena/util/text_utils.py:237

        Handles multiple brace sets as well.
        """
        patterns = [pattern]
        while any("{" in p or "}" in p for p in patterns):
            new_patterns = []
            for p in patterns:
                match = re.search(r"\{([^{}]*)\}", p)
                if match:
                    options_expr = match.group(1)
                    if not options_expr:
                        raise ValueError(f"Invalid glob brace expression in {pattern!r}: empty braces are not allowed")

                    prefix = p[: match.start()]
                    suffix = p[match.end() :]
                    options = options_expr.split(",")
                    for option in options:
                        new_patterns.append(f"{prefix}{option}{suffix}")
                else:
                    raise ValueError(f"Invalid glob brace expression in {pattern!r}: unmatched brace")
            patterns = new_patterns
        return patterns

    @staticmethod
    def _translate_glob_to_regex(pattern: str) -> str:
        res = []
        i = 0
        n = len(pattern)
        while i < n:
            c = pattern[i]
            i += 1
            if c == "*":
                if i < n and pattern[i] == "*":  # **
                    i += 1
                    if i < n and pattern[i] == "/":  # **/
                        i += 1
                        if res and res[-1] == "/":  # /**/
                            res = res[:-1]

View on GitHub (pinned to 7fcbca7e62)

Solutions

  1. Balance the braces in the pattern, e.g. 'src/*.{py,pyi}'
  2. Remove braces if no alternation is intended
  3. Pre-validate the pattern with a quick brace-balance check before calling
  4. Catch ValueError and surface a pattern-syntax error to the user

Example fix

// before
pattern = 'src/*{.py'
// after
pattern = 'src/*.{py}'  # or simply 'src/*.py'
Defensive patterns

Strategy: validation

Validate before calling

def braces_balanced(pattern: str) -> bool:
    return pattern.count("{") == pattern.count("}")
if not braces_balanced(glob):
    raise ValueError(f"unbalanced braces in {glob!r}")

Try / catch

try:
    expanded = GlobPattern(pat)
except ValueError as e:
    if "unmatched brace" in str(e):
        log.error("Fix pattern syntax: %s", pat)
    raise

Prevention

When it happens

Trigger: Passing patterns like 'src/*{.py' or 'a{b,c' where brace counts don't balance — the expansion loop terminates without consuming all braces and raises.

Common situations: Hand-written glob patterns with typos; string concatenation building patterns that cut off mid-brace; partially substituted templates.

Related errors


AI-assisted analysis of oraios/serena@7fcbca7e62 (2026-08-29). Data as JSON: /api/errors/be6c1133a2cefc67. Report an issue: GitHub.