can1357/oh-my-pi · error · ToolError

overlapping LSP edits: ${formatRange(earlier)} conflicts wit

Error message

overlapping LSP edits: ${formatRange(earlier)} conflicts with ${formatRange(later)}; LSP produced inconsistent edits

What it means

After sorting edits bottom-up and collapsing byte-identical duplicates, the validator walks adjacent pairs: each earlier (later-in-document) edit's range end must not extend past the next edit's start. If ranges overlap, applying bottom-up would clobber offsets and corrupt the file, so a ToolError names the two conflicting ranges.

Source

Thrown at packages/coding-agent/src/lsp/edits.ts:122

		})
		.map(entry => entry.edit);
	const unique: TextEdit[] = [];
	for (const edit of sorted) {
		const prev = unique[unique.length - 1];
		if (prev && !isEmptyRange(edit.range) && rangesEqual(prev.range, edit.range) && prev.newText === edit.newText) {
			continue;
		}
		unique.push(edit);
	}

	// Detect overlapping ranges: in reverse-sorted order, each edit's start
	// must be >= the next edit's end. If not, the edits would clobber each other
	// once applied bottom-up.
	for (let i = 0; i < unique.length - 1; i++) {
		const later = unique[i].range;
		const earlier = unique[i + 1].range;
		if (comparePosition(earlier.end, later.start) > 0) {
			throw new ToolError(
				`overlapping LSP edits: ${formatRange(earlier)} conflicts with ${formatRange(later)}; LSP produced inconsistent edits`,
			);
		}
	}

	return unique;
}

/**
 * Flatten a WorkspaceEdit's text edits into a Map<uri, TextEdit[]>.
 * Resource operations (create/rename/delete) are ignored — callers handle them separately.
 */
export function flattenWorkspaceTextEdits(edit: WorkspaceEdit): Map<string, TextEdit[]> {
	const out = new Map<string, TextEdit[]>();
	const push = (uri: string, edits: TextEdit[]) => {
		if (edits.length === 0) return;
		const prev = out.get(uri);
		if (prev) prev.push(...edits);

View on GitHub (pinned to 9690622007)

Solutions

  1. Request only one code action at a time instead of merging multiple actions' edits
  2. Fix or update the language server producing overlapping ranges
  3. Split application: apply each edit set to a fresh document state sequentially

Example fix

// before: merging edits from two actions
sortAndValidateTextEdits([...actionAEdits, ...actionBEdits]);
// after: apply sequentially
applyEdits(actionAEdits); applyEdits(refreshEdits(actionBEdits));
Defensive patterns

Strategy: validation

Validate before calling

function rangesOverlap(a, b) {
  const cmp = (p, q) => p.line - q.line || p.character - q.character;
  return !(cmp(a.end, b.start) <= 0 || cmp(b.end, a.start) <= 0);
}
for (let i = 0; i < edits.length; i++)
  for (let j = i + 1; j < edits.length; j++)
    if (rangesOverlap(edits[i].range, edits[j].range)) throw new Error('server produced overlapping edits');

Try / catch

try {
  const sorted = sortAndValidateTextEdits(edits);
} catch (err) {
  if (err.message.startsWith('overlapping LSP edits:')) {
    // fall back to applying edits one action at a time, re-querying between batches
  }
  throw err;
}

Prevention

When it happens

Trigger: A server returns two text edits whose ranges overlap after dedup (e.g. two code actions both covering the same lines, or a malformed computeEdit output); sortAndValidateTextEdits detects comparePosition(earlier.end, later.start) > 0.

Common situations: Server bugs producing inconsistent edits; combining edits from multiple code actions on overlapping regions; renamed/refactored server emitting stale ranges.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/039c214af425a100. Report an issue: GitHub.