oraios/serena · error · ValueError

expected_count={expected_count}, but the pattern matches {le

Error message

expected_count={expected_count}, but the pattern matches {len(occurrences)} occurrence(s) - NO changes were applied. Review the prospective changes below; re-issue with the corrected expectation, a refined pattern, or occurrence_ids selecting the intended subset.
{listing}

What it means

apply() supports an expected_count safety check: if specified (>= 0) and the number of pattern matches differs from it, no changes are applied and a ValueError including a dry-run-style listing of the prospective changes is raised. This protects against unintended bulk replacements when the file drifted or the pattern is broader than expected.

Source

Thrown at src/serena/tools/file_tools.py:305

                    f"{len(problems)} of the given occurrence_ids could not be resolved - NO changes were applied:\n"
                    f"{problem_lines}\n"
                    "Re-run with dry_run=True to obtain current occurrence ids."
                )
            if not selected:
                raise ValueError("occurrence_ids is empty - pass at least one id from a dry run, or omit the parameter to replace all.")
            return self._apply_occurrences(replacer, selected, contents, needle, repl)

        # 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:

View on GitHub (pinned to 7fcbca7e62)

Solutions

  1. Inspect the listing in the error to see every match, then re-issue with the correct expected_count
  2. Refine the pattern (or add path/glob restrictions) so it matches exactly the intended occurrences
  3. Use occurrence_ids from a dry run to select exactly the intended subset
  4. Set expected_count=-1 (omit) if the count is genuinely variable and the replacement is safe for all matches

Example fix

// before
agent.replace_content(needle="oldName", repl="newName", expected_count=1)
// after
listing = agent.replace_content(needle="oldName", repl="newName", dry_run=True)
# review matches, then select subset:
agent.replace_content(needle="oldName", repl="newName",
                      occurrence_ids=[listing.occurrences[0].id])
Defensive patterns

Strategy: try-catch

Validate before calling

dry = agent.replace_content(needle, repl, dry_run=True)
assert len(dry.occurrences) == expected_count, \
    f"{len(dry.occurrences)} matches, expected {expected_count}"

Type guard

def count_matches(needle, path) -> int:
    return len(agent.search_for_pattern(needle, path=path))

Try / catch

try:
    agent.replace_content(needle, repl, expected_count=n)
except ValueError as e:
    if "expected_count" in str(e):
        # listing of matches is embedded in the message; refine pattern or ids
        dry = agent.replace_content(needle, repl, dry_run=True)
        agent.replace_content(needle, repl, occurrence_ids=[o.id for o in dry.occurrences[:n]])
    else:
        raise

Prevention

When it happens

Trigger: Calling replace_content with expected_count=N where the pattern matches M != N occurrences; the target text appears more times than expected (e.g. in comments, tests, or generated code); a previous edit already changed the count.

Common situations: Refactoring scripts written against an earlier file version; needles that also match inside strings/comments/docstrings; case-insensitive or wildcard patterns matching more than the single intended site.

Related errors


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