{"record":{"id":"43a112ff0d5edb19","repo":"oraios/serena","slug":"match-is-ambiguous-the-search-pattern-matches-mul","errorCode":null,"errorMessage":"Match is ambiguous: the search pattern matches multiple overlapping occurrences. Please revise the search pattern to be more specific to avoid ambiguity, e.g. by matching specific context after the match, or try using the literal mode.","messagePattern":"Match is ambiguous: the search pattern matches multiple overlapping occurrences\\. Please revise the search pattern to be more specific to avoid ambiguity, e\\.g\\. by matching specific context after the match, or try using the literal mode\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/serena/util/text_utils.py","lineNumber":424,"sourceCode":"        :param regex_pattern: The regex pattern being used for matching\n        :param repl_template: The replacement template with $!1, $!2, etc. for backreferences\n        :param regex_flags: The flags to use when searching (e.g., re.DOTALL | re.MULTILINE)\n        :return: A function suitable for use with re.sub() or re.subn()\n        \"\"\"\n\n        def validate_and_replace(match: re.Match) -> str:\n            matched_text = match.group(0)\n\n            # For multi-line match, check if the same pattern matches again within the already-matched text,\n            # rendering the match ambiguous. Typical pattern in the code:\n            #    <start><other-stuff><start><stuff><end>\n            # When matching\n            #    <start>.*?<end>\n            # this will match the entire span above, while only the suffix may have been intended.\n            # (See test case for a practical example.)\n            # To detect this, we check if the same pattern matches again within the matched text,\n            if \"\\n\" in matched_text and re.search(regex_pattern, matched_text[1:], flags=regex_flags):\n                raise ValueError(\n                    \"Match is ambiguous: the search pattern matches multiple overlapping occurrences. \"\n                    \"Please revise the search pattern to be more specific to avoid ambiguity, \"\n                    \"e.g. by matching specific context after the match, or try using the literal mode.\"\n                )\n\n            # Handle backreferences: replace $!1, $!2, etc. with actual matched groups\n            def expand_backreference(m: re.Match) -> str:\n                group_num = int(m.group(1))\n                group_value = match.group(group_num)\n                return group_value if group_value is not None else m.group(0)\n\n            result = re.sub(r\"\\$!(\\d+)\", expand_backreference, repl_template)\n            return result\n\n        return validate_and_replace\n\n    def replace(\n        self,","sourceCodeStart":406,"sourceCodeEnd":442,"githubUrl":"https://github.com/oraios/serena/blob/7fcbca7e62555ec2287ddb2f083caee805848ea6/src/serena/util/text_utils.py#L406-L442","documentation":"Thrown by validate_and_replace in serena's text utilities when a multi-line regex match spans text in which the same pattern matches again (re.search against matched_text[1:] succeeds). This means the regex is 'greedy-ambiguous': it likely swallowed more than the intended occurrence, e.g. matching from the first <start> to a later <end> when only the suffix was wanted. The library raises instead of silently replacing the wrong span.","triggerScenarios":"Calling replace with a regex that uses start/end anchors matching multiple overlapping occurrences in multiline mode, where the matched text contains a second match of the same pattern.","commonSituations":"Editing config or source files where a marker like <!-- start -->...<!-- end --> appears more than once; regexes like 'def foo.*?return' matching across function bodies; literal strings that were accidentally regex-interpreted and match unexpectedly.","solutions":["Make the regex more specific by adding unique context after the end of the match (e.g. include the line following the end marker)","Switch to literal mode (mode='literal') if the needle is not really a regex","Split the replacement into two smaller, unique replacements instead of one spanning match","If multiple occurrences are truly intended, use a different API or pre-check the file content"],"exampleFix":"// before\nRegExpEditMode(mode='regex').replace(content, 'def foo.*?return', '...')  # matches to a later 'return' too\n// after\nRegExpEditMode(mode='regex').replace(content, r'def foo.*?return .*?\\n(?=\\n\\S)', '...')  # anchored with trailing context","handlingStrategy":"validation","validationCode":"m = re.search(regex_pattern, content, flags=re.MULTILINE|re.DOTALL)\nif m and '\\n' in m.group(0) and re.search(regex_pattern, m.group(0)[1:], flags=re.MULTILINE|re.DOTALL):\n    raise ValueError('pattern is ambiguous; add trailing context or use literal mode')","typeGuard":"def is_unambiguous(pattern: str, content: str) -> bool:\n    m = re.search(pattern, content, flags=re.DOTALL)\n    return m is not None and not ('\\n' in m.group(0) and re.search(pattern, m.group(0)[1:], flags=re.DOTALL))","tryCatchPattern":"try:\n    updated = editor.replace(content, pattern, repl)\nexcept ValueError as e:\n    if 'ambiguous' in str(e):\n        updated = editor.replace(content, pattern_with_more_context, repl)\n    else:\n        raise","preventionTips":["Always include unique trailing context in start/end-span regexes","Prefer literal mode when the needle is not a real regex","Test patterns against the real file content before applying edits"],"tags":["regex","text-processing","ambiguous-match"],"backgroundTag":"ambiguous-regex-match","analyzedSha":"7fcbca7e62555ec2287ddb2f083caee805848ea6","analyzedAt":"2026-08-29T00:04:09.619Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}