{"record":{"id":"02315db9b40f1f52","repo":"OpenBMB/ChatDev","slug":"edit-ranges-overlap-merge-them-before-calling-app","errorCode":null,"errorMessage":"edit ranges overlap; merge them before calling apply_text_edits","messagePattern":"edit ranges overlap; merge them before calling apply_text_edits","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"functions/function_calling/file.py","lineNumber":956,"sourceCode":"    start_line: int,\n    end_line: Optional[int],\n    replacement: Optional[str],\n) -> List[Mapping[str, Any]]:\n    effective_end = end_line if end_line is not None else start_line\n    payload = {\n        \"start_line\": start_line,\n        \"end_line\": effective_end,\n        \"replacement\": replacement if replacement is not None else \"\",\n    }\n    return [payload]\n\n\ndef _validate_edit_ranges(edits: Sequence[TextEdit]) -> None:\n    previous_range_end = 0\n    for edit in edits:\n        effective_end = max(edit.end_line, edit.start_line - 1)\n        if edit.start_line <= previous_range_end and previous_range_end > 0:\n            raise ValueError(\"edit ranges overlap; merge them before calling apply_text_edits\")\n        previous_range_end = max(previous_range_end, effective_end)\n\n\ndef _apply_edits_in_place(lines: MutableSequence[str], edits: Sequence[TextEdit]) -> None:\n    for edit in reversed(edits):\n        current_line_count = len(lines)\n        if edit.start_line > current_line_count + 1:\n            raise ValueError(\"start_line is beyond the end of the file\")\n        start_idx = min(edit.start_line - 1, current_line_count)\n        if start_idx > current_line_count:\n            raise ValueError(\"start_line is beyond the end of the file\")\n        removal_count = max(edit.end_line - edit.start_line + 1, 0)\n        if removal_count > 0:\n            end_line = min(edit.end_line, len(lines))\n            removal_count = max(end_line - edit.start_line + 1, 0)\n        end_idx = start_idx + removal_count\n        lines[start_idx:end_idx] = edit.replacement_lines\n","sourceCodeStart":938,"sourceCodeEnd":974,"githubUrl":"https://github.com/OpenBMB/ChatDev/blob/4fb2db0ea90375ce1059f44fe03ffbd191a7a169/functions/function_calling/file.py#L938-L974","documentation":"apply_text_edits requires edit ranges to be non-overlapping and sorted ascending; _validate_edit_ranges walks them in order and fails if an edit starts at or before the end of a previous edit (after accounting for pure deletions). Because edits are applied bottom-up in reverse order, overlapping ranges would corrupt positioning, so the library refuses them.","triggerScenarios":"Two edits touching the same lines, e.g. (3-5) and (4-6); edits generated out of ascending order; edits derived from multiple diff hunks that share a boundary line.","commonSituations":"Merging hunks from a patch file without de-duplicating; LLM emitting redundant overlapping edits; adding an edit at the end of a list that should have been merged with an earlier one.","solutions":["Merge overlapping edits into a single edit covering the union range with the combined replacement text","Sort edits by start_line ascending and verify ranges do not touch/overlap before calling","Recompute edit ranges from the original file snapshot rather than incrementally from prior edits"],"exampleFix":"# before\nedits=[{\"start_line\": 3, \"end_line\": 5, \"replacement\": \"a\"},\n       {\"start_line\": 4, \"end_line\": 6, \"replacement\": \"b\"}]\n# after\nedits=[{\"start_line\": 3, \"end_line\": 6, \"replacement\": \"a\\nb\"}]","handlingStrategy":"validation","validationCode":"def merge_overlaps(edits):\n    edits = sorted(edits, key=lambda e: e[\"start_line\"])\n    merged = []\n    for e in edits:\n        if merged and e[\"start_line\"] <= merged[-1].get(\"end_line\", merged[-1][\"start_line\"]):\n            prev = merged.pop()\n            e = {\"start_line\": prev[\"start_line\"],\n                 \"end_line\": max(e.get(\"end_line\", 0), prev.get(\"end_line\", 0)),\n                 \"replacement\": e.get(\"replacement\", \"\")}\n        merged.append(e)\n    return merged\n\napply_text_edits(path=path, edits=merge_overlaps(edits))","typeGuard":"def edits_disjoint_and_sorted(edits) -> bool:\n    prev_end = 0\n    for e in sorted(edits, key=lambda x: x[\"start_line\"]):\n        end = max(e.get(\"end_line\", e[\"start_line\"]), e[\"start_line\"] - 1)\n        if e[\"start_line\"] <= prev_end > 0:\n            return False\n        prev_end = max(prev_end, end)\n    return True","tryCatchPattern":"try:\n    apply_text_edits(path=path, edits=edits)\nexcept ValueError as e:\n    if \"overlap\" in str(e):\n        apply_text_edits(path=path, edits=merge_overlaps(edits))\n    else:\n        raise","preventionTips":["Sort and de-duplicate hunks before submitting","Apply all edits in one batch from a single file snapshot","Recompute ranges after any prior edit instead of reusing stale ones"],"tags":["validation","text-edit","overlap"],"backgroundTag":"overlapping-edit-ranges","analyzedSha":"4fb2db0ea90375ce1059f44fe03ffbd191a7a169","analyzedAt":"2026-08-27T14:35:29.622Z","schemaVersion":2},"datasetVersion":"2026-08-27T19:17:21.184Z"}