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
- Replace consecutive `...` markers with a single `...`.
- If you meant to show skipped content separately, put a real literal anchor line between the two gaps.
- 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
- One `...` skips any amount of content — never repeat it.
- Deduplicate gap markers when concatenating pattern fragments in generators.
- Review copy-pasted patterns for stray duplicate ellipsis lines.
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
- Operation ${operationNumber} needs visible current text.
- Operation ${operationNumber} has no visible current text.
- Operation ${operationNumber} pattern is too generic; include
- Unsupported language '{value}'. Supported: {}
- Unable to infer language from file extension: {}. Specify `l
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/8ed423d38a5d2927.
Report an issue: GitHub.