oraios/serena · error

Error: No matches of search expression found.

Error message

Error: No matches of search expression found.

What it means

replace() uses re.subn and raises when zero matches were found for the search expression. Since these replacements are meant to be surgical edits, a zero-match result indicates the needle no longer matches the content, and the library fails loudly instead of no-op-ing.

Source

Thrown at src/serena/util/text_utils.py:474

        :return: the updated content after performing the replacement
        """
        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

View on GitHub (pinned to 7fcbca7e62)

Solutions

  1. Print/search the actual content to confirm the exact current text and update the needle
  2. Escape regex metacharacters or switch to mode='literal'
  3. Relax the regex (e.g. use .*? for variable whitespace) if formatting drifts
  4. Check whether an earlier replacement in a chain already modified the target text

Example fix

// before
replacer.replace(content, 'def old_name(.*?', '...')  # function renamed
// after
if re.search(r'def old_name\(', content):
    ...
else:
    content = replacer.replace(content, r'def \w+\(', '...')  # flexible pattern
Defensive patterns

Strategy: validation

Validate before calling

if re.search(regex, content, flags=re.MULTILINE|re.DOTALL) is None:
    raise LookupError('needle not present; skipping replacement')

Try / catch

try:
    updated = editor.replace(content, needle, repl)
except ValueError as e:
    if 'No matches' in str(e):
        logging.warning('needle not found; content may have changed')
        updated = content
    else:
        raise

Prevention

When it happens

Trigger: Calling replace() where the needle/regex does not occur in content at all — due to a typo, changed file content, or a regex escaping mistake.

Common situations: Library/framework upgrade changed the file text so templates no longer match; editing files whose formatting (whitespace, quotes) differs from the assumed pattern; regex specials like '(' not escaped in literal-mode confusion.

Related errors


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