oraios/serena · error

Match is ambiguous: the search pattern matches multiple over

Error message

Match is ambiguous: the search pattern matches multiple overlapping occurrences. Please revise the search pattern to be more specific to avoid ambiguity, e.g. by matching specific context after the match, or try using the literal mode.

What it means

Thrown by validate_and_replace in serena's text utilities when a multi-line regex match spans text in which the same pattern matches again (re.search against matched_text[1:] succeeds). This means the regex is 'greedy-ambiguous': it likely swallowed more than the intended occurrence, e.g. matching from the first <start> to a later <end> when only the suffix was wanted. The library raises instead of silently replacing the wrong span.

Source

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

        :param regex_pattern: The regex pattern being used for matching
        :param repl_template: The replacement template with $!1, $!2, etc. for backreferences
        :param regex_flags: The flags to use when searching (e.g., re.DOTALL | re.MULTILINE)
        :return: A function suitable for use with re.sub() or re.subn()
        """

        def validate_and_replace(match: re.Match) -> str:
            matched_text = match.group(0)

            # For multi-line match, check if the same pattern matches again within the already-matched text,
            # rendering the match ambiguous. Typical pattern in the code:
            #    <start><other-stuff><start><stuff><end>
            # When matching
            #    <start>.*?<end>
            # this will match the entire span above, while only the suffix may have been intended.
            # (See test case for a practical example.)
            # To detect this, we check if the same pattern matches again within the matched text,
            if "\n" in matched_text and re.search(regex_pattern, matched_text[1:], flags=regex_flags):
                raise ValueError(
                    "Match is ambiguous: the search pattern matches multiple overlapping occurrences. "
                    "Please revise the search pattern to be more specific to avoid ambiguity, "
                    "e.g. by matching specific context after the match, or try using the literal mode."
                )

            # Handle backreferences: replace $!1, $!2, etc. with actual matched groups
            def expand_backreference(m: re.Match) -> str:
                group_num = int(m.group(1))
                group_value = match.group(group_num)
                return group_value if group_value is not None else m.group(0)

            result = re.sub(r"\$!(\d+)", expand_backreference, repl_template)
            return result

        return validate_and_replace

    def replace(
        self,

View on GitHub (pinned to 7fcbca7e62)

Solutions

  1. Make the regex more specific by adding unique context after the end of the match (e.g. include the line following the end marker)
  2. Switch to literal mode (mode='literal') if the needle is not really a regex
  3. Split the replacement into two smaller, unique replacements instead of one spanning match
  4. If multiple occurrences are truly intended, use a different API or pre-check the file content

Example fix

// before
RegExpEditMode(mode='regex').replace(content, 'def foo.*?return', '...')  # matches to a later 'return' too
// after
RegExpEditMode(mode='regex').replace(content, r'def foo.*?return .*?\n(?=\n\S)', '...')  # anchored with trailing context
Defensive patterns

Strategy: validation

Validate before calling

m = re.search(regex_pattern, content, flags=re.MULTILINE|re.DOTALL)
if m and '\n' in m.group(0) and re.search(regex_pattern, m.group(0)[1:], flags=re.MULTILINE|re.DOTALL):
    raise ValueError('pattern is ambiguous; add trailing context or use literal mode')

Type guard

def is_unambiguous(pattern: str, content: str) -> bool:
    m = re.search(pattern, content, flags=re.DOTALL)
    return m is not None and not ('\n' in m.group(0) and re.search(pattern, m.group(0)[1:], flags=re.DOTALL))

Try / catch

try:
    updated = editor.replace(content, pattern, repl)
except ValueError as e:
    if 'ambiguous' in str(e):
        updated = editor.replace(content, pattern_with_more_context, repl)
    else:
        raise

Prevention

When it happens

Trigger: Calling replace with a regex that uses start/end anchors matching multiple overlapping occurrences in multiline mode, where the matched text contains a second match of the same pattern.

Common situations: Editing config or source files where a marker like <!-- start -->...<!-- end --> appears more than once; regexes like 'def foo.*?return' matching across function bodies; literal strings that were accidentally regex-interpreted and match unexpectedly.

Related errors


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