can1357/oh-my-pi · error

Operation ${operationNumber} has adjacent ${GAP}; use one el

Error message

Operation ${operationNumber} has adjacent ${GAP}; use one ellipsis.

What it means

The pattern tokenizer throws when two gap markers (`...`) appear directly adjacent in the token stream with no literal text between them. Adjacent gaps are redundant — a single `...` already means 'skip any content' — so the parser fails fast instead of accepting a meaningless double ellipsis.

Source

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

	const tokens: PatternToken[] = [];
	let literal = "";
	let captureCount = 0;
	const selectionBoundaries: number[] = [];
	const selectionAtLineStart: boolean[] = [];
	const selectionRawOffsets: number[] = [];
	const flushLiteral = () => {
		if (literal === "") return;
		const normalized = normalizeText(literal).text;
		if (normalized !== "") tokens.push({ kind: "literal", text: literal, normalized });
		literal = "";
	};

	for (let index = 0; index < pattern.length; ) {
		const gapMarker = patternGapAt(pattern, index);
		if (gapMarker) {
			flushLiteral();
			if (tokens.at(-1)?.kind === "gap") {
				throw new Error(`Operation ${operationNumber} has adjacent ${GAP}; use one ellipsis.`);
			}
			const lineStart = pattern.lastIndexOf("\n", Math.max(0, index - 1)) + 1;
			const nextNewline = pattern.indexOf("\n", index + gapMarker.length);
			const lineEnd = nextNewline === -1 ? pattern.length : nextNewline;
			const before = pattern.slice(lineStart, index).replaceAll(SELECT_OPEN, "").replaceAll(SELECT_CLOSE, "").trim();
			const after = pattern
				.slice(index + gapMarker.length, lineEnd)
				.replaceAll(SELECT_OPEN, "")
				.replaceAll(SELECT_CLOSE, "")
				.trim();
			tokens.push({
				kind: "gap",
				captureIndex: captureCount++,
				lineBounded: before !== "" && after !== "",
			});
			index += gapMarker.length;
			continue;
		}

View on GitHub (pinned to 9690622007)

Solutions

  1. Replace consecutive `...` markers with a single `...`.
  2. If you meant to show skipped content separately, put a real literal anchor line between the two gaps.
  3. If the extra gap was accidental from copy-paste, delete the duplicate line.

Example fix

// before
const pattern = "function foo() {\n...\n...\n}";
// after
const pattern = "function foo() {\n...\n}";
Defensive patterns

Strategy: validation

Validate before calling

if (/\.\.\.[\s]*\.\.\./.test(pattern)) {
  throw new Error("pattern contains adjacent ... markers; collapse to one");
}

Type guard

function hasAdjacentGaps(pattern: string): boolean {
  return /\.\.\.\s*\.\.\./.test(pattern);
}

Try / catch

try {
  await applySloppyEdit({ pattern });
} catch (err) {
  if (err instanceof Error && err.message.includes("adjacent") && err.message.includes("ellipsis")) {
    pattern = pattern.replace(/(\.\.\.\s*)+\.\.\./g, "...");
    return applySloppyEdit({ pattern });
  }
  throw err;
}

Prevention

When it happens

Trigger: Writing a pattern like `foo();\n...\n...\nbar();` or `... ...` where two ellipses touch with only whitespace between them. Detected in tokenizePattern when a new gap marker is seen while the last token is already kind "gap".

Common situations: Doubling ellipses to 'skip more lines' (misconception — one gap skips any amount); copy-paste artifacts leaving two `...` lines next to each other; template-generated patterns concatenating gap fragments without deduplication.

Related errors


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