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
- Make the regex more specific by adding unique context after the end of the match (e.g. include the line following the end marker)
- Switch to literal mode (mode='literal') if the needle is not really a regex
- Split the replacement into two smaller, unique replacements instead of one spanning match
- 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
- Always include unique trailing context in start/end-span regexes
- Prefer literal mode when the needle is not a real regex
- Test patterns against the real file content before applying edits
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
- Error: No matches of search expression found.
- Expression matches {n} occurrences. Please revise the expres
- No match found for regex: {regex}
- Match must be unique; found {len(matches)} matches for regex
- Regex must contain exactly one group to capture the position
AI-assisted analysis of oraios/serena@7fcbca7e62 (2026-08-29).
Data as JSON: /api/errors/43a112ff0d5edb19.
Report an issue: GitHub.