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

  1. Merge overlapping edits into a single edit covering the union range.
  2. Filter out redundant edits before calling the API, keeping the highest-priority one.
  3. Split the request into multiple sequential ApplyWorkspaceEdit calls with non-overlapping ranges.
  4. 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

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


AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29). Data as JSON: /api/errors/17f14f2754e233d1. Report an issue: GitHub.