oraios/serena · error
Expression matches {n} occurrences. Please revise the expres
Error message
Expression matches {n} occurrences. Please revise the expression to be more specific or enable allow_multiple_occurrences if this is expected. What it means
replace() expects the expression to match exactly once unless allow_multiple_occurrences is set. When re.subn reports n > 1 matches, the library refuses to replace all of them blindly and asks you to make the expression more specific or opt in explicitly.
Source
Thrown at src/serena/util/text_utils.py:476
if self.mode == "literal":
regex = re.escape(needle)
elif self.mode == "regex":
regex = needle
else:
raise ValueError(f"Invalid mode: '{self.mode}', expected 'literal' or 'regex'.")
regex_flags = (re.MULTILINE | re.DOTALL) if self.regex_multiline else 0
# create replacement function with validation and backreference handling
repl_fn = self._create_replacement_function(regex, repl, regex_flags=regex_flags)
# perform replacement
updated_content, n = re.subn(regex, repl_fn, content, flags=regex_flags)
if n == 0:
raise ValueError("Error: No matches of search expression found.")
if not self.allow_multiple_occurrences and n > 1:
raise ValueError(
f"Expression matches {n} occurrences. "
"Please revise the expression to be more specific or enable allow_multiple_occurrences if this is expected."
)
return updated_content
@dataclass
class ReplacementOccurrence:
"""A single prospective replacement of a pattern match within one file."""
occurrence_id: str
"""stable, content-anchored identifier: '<relative_path>:<index_in_file>@<digest>'"""
relative_path: str
index_in_file: int
"""0-based index of this match among the matches within its file (in position order)"""
start: int
"""character offset of the match start within the file content"""
end: intView on GitHub (pinned to 7fcbca7e62)
Solutions
- Add surrounding unique context to the needle so it matches only the intended occurrence
- Set allow_multiple_occurrences=True if replacing all occurrences is intended
- Use an index/offset-based replacement or loop with per-occurrence context
- Include a larger anchor span (function signature, section header) in the pattern
Example fix
// before replacer.replace(content, 'timeout=30', 'timeout=60') # appears 3 times // after replacer.replace(content, r'(client_config:.*?timeout=)30', r'\g<1>60') # scoped, unique # or: TextReplacement(..., allow_multiple_occurrences=True)
Defensive patterns
Strategy: validation
Validate before calling
n = len(re.findall(regex, content, flags=re.MULTILINE|re.DOTALL))
if n > 1 and not allow_multiple:
raise ValueError(f'{n} matches; narrow the pattern or set allow_multiple_occurrences') Try / catch
try:
updated = editor.replace(content, needle, repl)
except ValueError as e:
if 'matches' in str(e) and 'occurrences' in str(e):
editor.allow_multiple_occurrences = True
updated = editor.replace(content, needle, repl)
else:
raise Prevention
- Scope patterns with unique surrounding context
- Count matches with re.findall before editing
- Opt in to allow_multiple_occurrences only when replace-all is intended
When it happens
Trigger: Calling replace() with a pattern matching 2+ places in content while allow_multiple_occurrences=False (the default), e.g. a generic needle like 'version = ...' repeated in several sections.
Common situations: Editing boilerplate that appears in many files or blocks of the same file; overly broad regexes like '\\d+' or repeated comment markers; literal strings that occur in both header and body.
Related errors
- Match must be unique; found {len(matches)} matches for regex
- Match is ambiguous: the search pattern matches multiple over
- Error: No matches of search expression found.
- No match found for regex: {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/04c247793c8723ed.
Report an issue: GitHub.