oraios/serena · error · ValueError
{len(ambiguous)} occurrence(s) are ambiguous (the pattern ma
Error message
{len(ambiguous)} occurrence(s) are ambiguous (the pattern matches again inside the matched text, indicating possible over-matching) - NO changes were applied. Review the prospective changes below and either refine the pattern or explicitly select occurrences via occurrence_ids.
{listing} What it means
After the count check, apply() detects ambiguous occurrences: matches where the pattern also matches again inside the already-matched text (nested/overlapping matches), which would make replacement order-dependent and possibly destructive. If any ambiguous occurrence exists, nothing is replaced and a listing is shown.
Source
Thrown at src/serena/tools/file_tools.py:313
# blind apply (no ids)
if not occurrences:
raise ValueError(
"No occurrences of the pattern were found - NO changes were applied. "
"Check the mode (a literal needle containing regex metacharacters must use mode 'literal'; "
"wildcards require mode 'regex') and the path/glob restrictions, "
"or locate the content with search_for_pattern first."
)
if expected_count >= 0 and len(occurrences) != expected_count:
listing = self._render_listing(replacer, occurrences, contents, max_answer_chars, dry_run=False)
raise ValueError(
f"expected_count={expected_count}, but the pattern matches {len(occurrences)} occurrence(s) - "
f"NO changes were applied. Review the prospective changes below; re-issue with the corrected "
f"expectation, a refined pattern, or occurrence_ids selecting the intended subset.\n{listing}"
)
ambiguous = [o for o in occurrences if o.is_ambiguous]
if ambiguous:
listing = self._render_listing(replacer, occurrences, contents, max_answer_chars, dry_run=False)
raise ValueError(
f"{len(ambiguous)} occurrence(s) are ambiguous (the pattern matches again inside the matched text, "
f"indicating possible over-matching) - NO changes were applied. Review the prospective changes below "
f"and either refine the pattern or explicitly select occurrences via occurrence_ids.\n{listing}"
)
return self._apply_occurrences(replacer, occurrences, contents, needle, repl)
def _collect_files(self, relative_path: str, paths_include_glob: str, paths_exclude_glob: str) -> list[tuple[str, str]]:
"""Collects (relative_path, content) pairs of the non-ignored files in scope, in sorted path order."""
relative_path = relative_path.strip()
if relative_path:
self.project.validate_relative_path(relative_path, require_not_ignored=True)
abs_path = os.path.join(self.get_project_root(), relative_path)
if not os.path.exists(abs_path):
raise FileNotFoundError(f"Relative path {relative_path} does not exist.")
if os.path.isfile(abs_path):
rel_paths = [relative_path]
else:
_dirs, rel_paths = scan_directory(View on GitHub (pinned to 7fcbca7e62)
Solutions
- Refine the pattern with tighter boundaries (anchors, word boundaries, negated classes) so matches cannot nest
- Run with dry_run=True and explicitly select non-ambiguous occurrences via occurrence_ids
- Reduce the replacement to a literal needle if wildcards are unnecessary
- Review the listing in the message to see exactly which matches overlap and adjust accordingly
Example fix
// before agent.replace_content(needle="a+", repl="b", mode="regex") // after agent.replace_content(needle="(?<![a])a+(?![a])", repl="b", mode="regex")
Defensive patterns
Strategy: validation
Validate before calling
dry = agent.replace_content(needle, repl, dry_run=True) assert not any(o.is_ambiguous for o in dry.occurrences), "pattern has nested matches" agent.replace_content(needle, repl, occurrence_ids=[o.id for o in dry.occurrences])
Type guard
def unambiguous(occurrences) -> bool:
return all(not getattr(o, 'is_ambiguous', False) for o in occurrences) Try / catch
try:
agent.replace_content(needle, repl)
except ValueError as e:
if "ambiguous" in str(e):
dry = agent.replace_content(needle, repl, dry_run=True)
safe = [o.id for o in dry.occurrences if not o.is_ambiguous]
agent.replace_content(needle, repl, occurrence_ids=safe)
else:
raise Prevention
- Prefer literal needles over wildcard regex when possible
- Add boundaries (anchors/negated classes) to prevent nested matches
- Dry-run and inspect ambiguity flags before applying
When it happens
Trigger: Regex patterns that can match nested text, e.g. a wildcard like "f(.*?x" inside a longer match, or replacing strings that contain the needle itself (replacing 'foo' where text contains 'foofoo').
Common situations: Greedy-looking but actually overlapping patterns; replacing delimiters or repeated tokens (e.g. replacing "a" inside "aaa"); patterns built from user input that inadvertently nest.
Related errors
- No occurrences of the pattern were found - NO changes were a
- Found multiple {len(symbol_candidates)} symbols matching '{n
- {len(problems)} of the given occurrence_ids could not be res
- occurrence_ids is empty - pass at least one id from a dry ru
- expected_count={expected_count}, but the pattern matches {le
AI-assisted analysis of oraios/serena@7fcbca7e62 (2026-08-29).
Data as JSON: /api/errors/a07e2eed2ec663cc.
Report an issue: GitHub.