can1357/oh-my-pi · error

Operation ${operationNumber} has an unclosed selection marke

Error message

Operation ${operationNumber} has an unclosed selection marker ⟪; add closing ⟫. | Operation ${operationNumber} has an unmatched closing selection marker ⟫; add opening ⟪.

What it means

validateSelectionMarkers counts ⟪ open and ⟫ close markers in an operation pattern and requires them to balance. Selection markers denote 'replace exactly this span'; an unbalanced count means the model either never closed a selection or closed one it never opened, so the replacement target is ambiguous.

Source

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

			selected = true;
			index += SELECT_OPEN.length;
		} else if (pattern.startsWith(SELECT_CLOSE, index)) {
			selected = false;
			index += SELECT_CLOSE.length;
		} else if (selected && pattern.startsWith(SELECT_DIVIDER, index)) {
			return true;
		} else {
			index++;
		}
	}
	return false;
}

function validateSelectionMarkers(pattern: string, operationNumber: number): void {
	const openCount = (pattern.match(/⟪/gu) || []).length;
	const closeCount = (pattern.match(/⟫/gu) || []).length;
	if (openCount === closeCount) return;
	throw new Error(
		openCount > closeCount
			? `Operation ${operationNumber} has an unclosed selection marker ⟪; add closing ⟫.`
			: `Operation ${operationNumber} has an unmatched closing selection marker ⟫; add opening ⟪.`,
	);
}

/**
 * Resolve a selection whose content contains the divider character itself
 * (box-drawing code). A trailing divider reads as a deletion of the content
 * before it with the inner dividers literal; an odd count splits at the
 * middle divider (a symmetric replacement keeps equal literals on each
 * side); an even count reads as a deletion of the whole selected text. A
 * wrong guess still fails loud downstream: the resolved old side must match
 * the file.
 */
function resolveLiteralDividers(
	selected: string,
	operationNumber: number,

View on GitHub (pinned to 9690622007)

Solutions

  1. Add the missing ⟫ after the selected text for every unclosed ⟪
  2. Delete the stray ⟫ or add the missing ⟪ before it
  3. Re-send the full corrected payload — ops apply atomically, so all operations must be re-submitted

Example fix

// before
«old ⟪text»new
// after
«old ⟪text⟫»new
Defensive patterns

Strategy: validation

Validate before calling

const opens = (pattern.match(/⟪/gu) ?? []).length;
const closes = (pattern.match(/⟫/gu) ?? []).length;
if (opens !== closes) throw new Error(`unbalanced selection markers: ${opens} ⟪ vs ${closes} ⟫`);

Try / catch

try {
  applySloppy(content, input, ctx);
} catch (err) {
  if (err instanceof Error && err.message.includes('selection marker')) {
    // fix markers in payload and re-send full corrected payload
  } else throw err;
}

Prevention

When it happens

Trigger: Calling applySloppy with an operation whose pattern contains an odd/extra ⟪ without ⟫ (unclosed) or an ⟫ without ⟪ (unmatched closing).

Common situations: An LLM truncates output mid-selection; a stray ⟫ character in code being quoted; copy/paste dropping the closing marker line.

Related errors


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