can1357/oh-my-pi · error · Error

Operations ${previous.operationNumber} and ${current.operati

Error message

Operations ${previous.operationNumber} and ${current.operationNumber} target overlapping original spans near lines ${firstLine} and ${secondLine}.
Conflicting candidates:
Operation ${previous.operationNumber} near line ${firstLine}:
${operationPayload(operations[previous.operationNumber - 1])}
Operation ${current.operationNumber} near line ${secondLine}:
${operationPayload(operations[current.operationNumber - 1])}
Keep whichever states the intended final text and drop the other.

What it means

After planning edits, the library verifies that operations target disjoint original spans. If candidates from two different operations overlap, applying both would conflict; overlapping candidates from the same operation are ignored as fuzzy-matcher artifacts, but across operations it throws. The message shows both operation payloads with the line numbers they target and advises keeping whichever states the intended final text.

Source

Thrown at packages/coding-agent/src/edit/sloppy.ts:3839

			continue;
		}
		// Overlapping spans are only a conflict when they disagree. A broad
		// `«*` plus a narrower op over one of its matches (rename + the line
		// that contains it) produces byte-identical text for the shared
		// region — merge instead of rejecting a payload that is consistent.
		const merged = reconcileOverlap(content, previous, current);
		if (merged) {
			ordered[ordered.length - 1] = merged;
			continue;
		}
		if (previous.operationNumber === current.operationNumber) {
			// Overlapping candidates of the SAME operation are a fuzzy-matcher
			// artifact (a pattern re-matching inside its own span); keep the first.
			continue;
		}
		const firstLine = lineNumberAt(content, previous.start);
		const secondLine = lineNumberAt(content, current.start);
		throw new Error(
			[
				`Operations ${previous.operationNumber} and ${current.operationNumber} target overlapping original spans near lines ${firstLine} and ${secondLine}.`,
				"Conflicting candidates:",
				`Operation ${previous.operationNumber} near line ${firstLine}:\n${operationPayload(operations[previous.operationNumber - 1])}`,
				`Operation ${current.operationNumber} near line ${secondLine}:\n${operationPayload(operations[current.operationNumber - 1])}`,
				"Keep whichever states the intended final text and drop the other.",
			].join("\n\n"),
		);
	}
	let result = content;
	for (const edit of ordered.reverse()) {
		result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);
	}
	if (result === content) throwNoOp(undefined, { content, offset: lastMatchOffset });
	noOpByPath.delete(context.path);
	context.notes?.push(...recoveryNotes, ...deletionNotes.values());
	return result;
}

View on GitHub (pinned to 9690622007)

Solutions

  1. Delete one of the two conflicting operations, keeping the one whose REWRITE states the intended final text.
  2. Narrow one operation's PATTERN so its span no longer intersects the other's.
  3. Merge the two edits into a single operation covering the shared region.

Example fix

// before (two ops both cover lines 10-12)
» 1  ... spans 10-12 ...
» 2  ... spans 11-13 ...

// after (single op)
» 1  ... spans 10-12, REWRITE contains final text for all ...
Defensive patterns

Strategy: validation

Validate before calling

function assertDisjointSpans(planned: { start: number; end: number; n: number }[]) {
  const sorted = [...planned].sort((a, b) => a.start - b.start);
  for (let i = 1; i < sorted.length; i++) {
    if (sorted[i].start < sorted[i - 1].end && sorted[i].n !== sorted[i - 1].n)
      throw new Error(`Operations ${sorted[i-1].n} and ${sorted[i].n} overlap`);
  }
}

Type guard

const overlaps = (a: { start: number; end: number }, b: { start: number; end: number }): boolean =>
  a.start < b.end && b.start < a.end;

Try / catch

try {
  applySloppyEdit(payload);
} catch (err) {
  if (err instanceof Error && err.message.includes("target overlapping original spans")) {
    payload = dropConflictingOperation(payload, err.message);
    applySloppyEdit(payload);
  } else throw err;
}

Prevention

When it happens

Trigger: applySloppyEdit where candidate spans [previous.start, previous.end) and [current.start, current.end) intersect for different operationNumbers, detected while scanning ordered candidates (sloppy.ts:3839).

Common situations: Two edits rewriting overlapping regions (e.g. one replaces a block, another tweaks a line inside it); a broad PATTERN accidentally matching a region a later op also edits; payload assembled by concatenating edits to the same area.

Related errors


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