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

  1. Refine the pattern with tighter boundaries (anchors, word boundaries, negated classes) so matches cannot nest
  2. Run with dry_run=True and explicitly select non-ambiguous occurrences via occurrence_ids
  3. Reduce the replacement to a literal needle if wildcards are unnecessary
  4. 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

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


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