{"record":{"id":"a07e2eed2ec663cc","repo":"oraios/serena","slug":"len-ambiguous-occurrence-s-are-ambiguous-the","errorCode":null,"errorMessage":"{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.\n{listing}","messagePattern":"(.+?) 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\\.\n(.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/serena/tools/file_tools.py","lineNumber":313,"sourceCode":"        # blind apply (no ids)\n        if not occurrences:\n            raise ValueError(\n                \"No occurrences of the pattern were found - NO changes were applied. \"\n                \"Check the mode (a literal needle containing regex metacharacters must use mode 'literal'; \"\n                \"wildcards require mode 'regex') and the path/glob restrictions, \"\n                \"or locate the content with search_for_pattern first.\"\n            )\n        if expected_count >= 0 and len(occurrences) != expected_count:\n            listing = self._render_listing(replacer, occurrences, contents, max_answer_chars, dry_run=False)\n            raise ValueError(\n                f\"expected_count={expected_count}, but the pattern matches {len(occurrences)} occurrence(s) - \"\n                f\"NO changes were applied. Review the prospective changes below; re-issue with the corrected \"\n                f\"expectation, a refined pattern, or occurrence_ids selecting the intended subset.\\n{listing}\"\n            )\n        ambiguous = [o for o in occurrences if o.is_ambiguous]\n        if ambiguous:\n            listing = self._render_listing(replacer, occurrences, contents, max_answer_chars, dry_run=False)\n            raise ValueError(\n                f\"{len(ambiguous)} occurrence(s) are ambiguous (the pattern matches again inside the matched text, \"\n                f\"indicating possible over-matching) - NO changes were applied. Review the prospective changes below \"\n                f\"and either refine the pattern or explicitly select occurrences via occurrence_ids.\\n{listing}\"\n            )\n        return self._apply_occurrences(replacer, occurrences, contents, needle, repl)\n\n    def _collect_files(self, relative_path: str, paths_include_glob: str, paths_exclude_glob: str) -> list[tuple[str, str]]:\n        \"\"\"Collects (relative_path, content) pairs of the non-ignored files in scope, in sorted path order.\"\"\"\n        relative_path = relative_path.strip()\n        if relative_path:\n            self.project.validate_relative_path(relative_path, require_not_ignored=True)\n        abs_path = os.path.join(self.get_project_root(), relative_path)\n        if not os.path.exists(abs_path):\n            raise FileNotFoundError(f\"Relative path {relative_path} does not exist.\")\n        if os.path.isfile(abs_path):\n            rel_paths = [relative_path]\n        else:\n            _dirs, rel_paths = scan_directory(","sourceCodeStart":295,"sourceCodeEnd":331,"githubUrl":"https://github.com/oraios/serena/blob/7fcbca7e62555ec2287ddb2f083caee805848ea6/src/serena/tools/file_tools.py#L295-L331","documentation":"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.","triggerScenarios":"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').","commonSituations":"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.","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"],"exampleFix":"// before\nagent.replace_content(needle=\"a+\", repl=\"b\", mode=\"regex\")\n// after\nagent.replace_content(needle=\"(?<![a])a+(?![a])\", repl=\"b\", mode=\"regex\")","handlingStrategy":"validation","validationCode":"dry = agent.replace_content(needle, repl, dry_run=True)\nassert not any(o.is_ambiguous for o in dry.occurrences), \"pattern has nested matches\"\nagent.replace_content(needle, repl, occurrence_ids=[o.id for o in dry.occurrences])","typeGuard":"def unambiguous(occurrences) -> bool:\n    return all(not getattr(o, 'is_ambiguous', False) for o in occurrences)","tryCatchPattern":"try:\n    agent.replace_content(needle, repl)\nexcept ValueError as e:\n    if \"ambiguous\" in str(e):\n        dry = agent.replace_content(needle, repl, dry_run=True)\n        safe = [o.id for o in dry.occurrences if not o.is_ambiguous]\n        agent.replace_content(needle, repl, occurrence_ids=safe)\n    else:\n        raise","preventionTips":["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"],"tags":["regex","ambiguity","replace"],"backgroundTag":"ambiguous-pattern-match","analyzedSha":"7fcbca7e62555ec2287ddb2f083caee805848ea6","analyzedAt":"2026-08-29T00:04:09.619Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}