can1357/oh-my-pi · error · Error

Operation ${operationNumber} has selection markers in REWRIT

Error message

Operation ${operationNumber} has selection markers in REWRITE; PATTERN is current text, REWRITE is final text.

What it means

The sloppy edit DSL validates each operation's REWRITE block before applying it. Selection markers (the SELECT_OPEN/SELECT_CLOSE delimiters used in PATTERN to mark selected spans) are not valid in REWRITE, because PATTERN describes the current file text and REWRITE is the final replacement text written verbatim. The library throws this in renderRewrite to stop the user from leaking pattern-only syntax into the output file.

Source

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

			.split("\n")
			.map(line => line.trim()),
	);
	for (let forward = 0; forward < 12 && forward < after.length; forward++) {
		const line = after[forward];
		if (!usable(line)) continue;
		if (othersAfter.every(lines => !lines.includes(line))) return { side: "after", line };
	}
	return undefined;
}

function renderRewrite(
	rewrite: string,
	selectedCaptureIndices: number[],
	captures: string[],
	operationNumber: number,
): string {
	if (rewrite.includes(SELECT_OPEN) || rewrite.includes(SELECT_CLOSE)) {
		throw new Error(
			`Operation ${operationNumber} has selection markers in REWRITE; PATTERN is current text, REWRITE is final text.`,
		);
	}
	const sentinels = selectedCaptureIndices.map((_, index) => `\u0000V8GAP${index}\u0000`);
	let markerIndex = 0;
	let marked = "";
	for (let index = 0; index < rewrite.length; ) {
		const gapMarker = rewrite.startsWith(GAP, index) ? GAP : undefined;
		if (gapMarker) {
			const lineStart = rewrite.lastIndexOf("\n", index - 1) + 1;
			const nextNewline = rewrite.indexOf("\n", index);
			const lineEnd = nextNewline === -1 ? rewrite.length : nextNewline;
			const line = rewrite.slice(lineStart, lineEnd);
			if (markerIndex >= sentinels.length) {
				// An unclaimed gap alone on its line is context elision, never final
				// text; writing it verbatim splices a literal `…` into the file.
				if (line.trim() === GAP) {
					throw new Error(

View on GitHub (pinned to 9690622007)

Solutions

  1. Remove the selection markers from the REWRITE block; REWRITE must contain only final text.
  2. Rebuild REWRITE by typing the intended final lines rather than copying PATTERN.
  3. Verify the marker alphabet in docs (markers are re-skinned per variant) and ensure REWRITE contains none of them.

Example fix

// before
» 1
<sel>const x = 1</sel>
====
<sel>const x = 2</sel>

// after
» 1
<sel>const x = 1</sel>
====
const x = 2
Defensive patterns

Strategy: validation

Validate before calling

const SELECT_OPEN = "<sel>"; const SELECT_CLOSE = "</sel>";
function assertNoSelectionMarkers(operation: { rewriteText: string; n: number }) {
  if (operation.rewriteText.includes(SELECT_OPEN) || operation.rewriteText.includes(SELECT_CLOSE)) {
    throw new Error(`Operation ${operation.n}: REWRITE must not contain selection markers`);
  }
}

Type guard

const hasSelectionMarkers = (s: string, open: string, close: string): boolean =>
  s.includes(open) || s.includes(close);

Try / catch

try {
  applySloppyEdit(payload);
} catch (err) {
  if (err instanceof Error && err.message.includes("selection markers in REWRITE")) {
    payload = stripSelectionMarkersFromRewrites(payload);
    applySloppyEdit(payload);
  } else throw err;
}

Prevention

When it happens

Trigger: Calling the sloppy apply path with an operation whose REWRITE block contains the selection open/close markers (copied from PATTERN instead of retyped), e.g. pasting the whole PATTERN block as REWRITE unchanged.

Common situations: Authors copy the PATTERN block as a starting point for REWRITE and forget to delete the selection markers; generation pipelines that template REWRITE from PATTERN; hand-edited payloads where the marker alphabet is easy to confuse with ordinary text.

Related errors


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