charmbracelet/crush · error
overlapping edits detected between edit %d and %d
Error message
overlapping edits detected between edit %d and %d
What it means
Before applying edits, applyTextEdits performs an O(n^2) pairwise check for overlapping protocol.TextEdit ranges. If two edits touch overlapping regions of the document, applying both would produce undefined/corrupted results, so the operation is rejected with the indices of the conflicting edits.
Source
Thrown at internal/lsp/util/edit.go:44
// Detect line ending style
var lineEnding string
if bytes.Contains(content, []byte("\r\n")) {
lineEnding = "\r\n"
} else {
lineEnding = "\n"
}
// Track if file ends with a newline
endsWithNewline := len(content) > 0 && bytes.HasSuffix(content, []byte(lineEnding))
// Split into lines without the endings
lines := strings.Split(string(content), lineEnding)
// Check for overlapping edits
for i, edit1 := range edits {
for j := i + 1; j < len(edits); j++ {
if rangesOverlap(edit1.Range, edits[j].Range) {
return fmt.Errorf("overlapping edits detected between edit %d and %d", i, j)
}
}
}
// Sort edits in reverse order
sortedEdits := make([]protocol.TextEdit, len(edits))
copy(sortedEdits, edits)
sort.Slice(sortedEdits, func(i, j int) bool {
if sortedEdits[i].Range.Start.Line != sortedEdits[j].Range.Start.Line {
return sortedEdits[i].Range.Start.Line > sortedEdits[j].Range.Start.Line
}
return sortedEdits[i].Range.Start.Character > sortedEdits[j].Range.Start.Character
})
// Apply each edit
for _, edit := range sortedEdits {
newLines, err := applyTextEdit(lines, edit, encoding)
if err != nil {View on GitHub (pinned to 7944b8e522)
Solutions
- Merge overlapping edits into a single edit covering the union range.
- Filter out redundant edits before calling the API, keeping the highest-priority one.
- Split the request into multiple sequential ApplyWorkspaceEdit calls with non-overlapping ranges.
- Normalize/sort edit ranges and deduplicate identical ranges beforehand.
Example fix
// before edits := append(quickFixEdits, renameEdits...) util.ApplyWorkspaceEdit(ctx, edit) // after edits := dedupeNonOverlapping(quickFixEdits, renameEdits) util.ApplyWorkspaceEdit(ctx, edit)
Defensive patterns
Strategy: validation
Validate before calling
func hasOverlap(a, b protocol.Range) bool {
return a.Start.Line <= b.End.Line && b.Start.Line <= a.End.Line
}
for i := 0; i < len(edits); i++ {
for j := i + 1; j < len(edits); j++ {
if hasOverlap(edits[i].Range, edits[j].Range) {
return fmt.Errorf("edits %d and %d overlap", i, j)
}
}
} Try / catch
if strings.Contains(err.Error(), "overlapping edits") {
edits := mergeOverlapping(edits) // then retry once
} Prevention
- Deduplicate edits when merging results from multiple LSP features.
- Keep one edit per source diagnostic/symbol.
- Normalize and sort edit ranges before batching.
When it happens
Trigger: ApplyWorkspaceEdit receiving an array of TextEdit where edit[i].Range and edit[j].Range intersect (e.g. two edits covering the same lines, or an insertion inside another edit's span).
Common situations: Multiple LSP code actions or refactoring results merged into one edit list without deduplication; a quickfix and a rename both targeting the same symbol; clients batching edits from several diagnostics.
Related errors
- invalid URI: %w
- failed to read file: %w
- failed to apply edit: %w
- invalid start line: %d
- target file already exists and overwrite is not allowed: %s
AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29).
Data as JSON: /api/errors/17f14f2754e233d1.
Report an issue: GitHub.