can1357/oh-my-pi · error

Operation ${operationNumber} mixes inline and bare selection

Error message

Operation ${operationNumber} mixes inline and bare selections. Use ⟪old|new⟫ for every selection, or use a » rewrite for all bare selections.

What it means

Within one operation, selections must use a single style: either every selection is inline '⟪old│new⟫' (sawInline) or every selection is bare '⟪…⟫' whose replacement comes from a » rewrite block (sawBare). Mixing both styles in one operation is ambiguous and rejected.

Source

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

			sawBare = true;
			patternText += pattern.slice(index, close + SELECT_CLOSE.length);
		} else {
			sawInline = true;
			if (selected.indexOf(SELECT_DIVIDER, divider + SELECT_DIVIDER.length) === -1) {
				patternText += `${SELECT_OPEN}${selected.slice(0, divider)}${SELECT_CLOSE}`;
				replacements.push(selected.slice(divider + SELECT_DIVIDER.length));
			} else {
				const resolved = resolveLiteralDividers(selected, operationNumber);
				patternText += `${SELECT_OPEN}${resolved.old}${SELECT_CLOSE}`;
				replacements.push(resolved.desired);
				notes.push(resolved.note);
			}
		}
		index = close + SELECT_CLOSE.length;
	}

	if (sawBare && sawInline) {
		throw new Error(
			`Operation ${operationNumber} mixes inline and bare selections. Use ${SELECT_OPEN}old${SELECT_DIVIDER}new${SELECT_CLOSE} for every selection, or use a ${REWRITE_HEADER} rewrite for all bare selections.`,
		);
	}
	return { patternText, replacements, notes };
}

/**
 * Embed selection-only lines into the anchor line above them. Models write
 * `anchorLine` then `⟪old│new⟫` on the next line to mean "change old inside
 * that anchor"; literally the selection would match the next occurrence
 * below instead. Relocates only when old occurs exactly once in the anchor.
 */
function relocateSelectionLines(patternText: string): string {
	const lines = patternText.split("\n");
	for (let index = 1; index < lines.length; index++) {
		const previous = lines[index - 1];
		const only = lines[index].trim().match(/^⟪([^⟪⟫]*)⟫$/u);
		if (!only || previous.includes(SELECT_OPEN) || previous.includes(GAP) || previous.trim() === "") continue;

View on GitHub (pinned to 9690622007)

Solutions

  1. Convert all selections to inline ⟪old│new⟫ form within the operation
  2. Or move all replacements into a » rewrite block and make every selection bare
  3. Restore the missing │ divider if one selection was meant to be inline

Example fix

// before
«x ⟪a│b⟫ ⟪c⟫»y
// after
«x ⟪a│b⟫ ⟪c│y⟫»
Defensive patterns

Strategy: validation

Validate before calling

const selections = pattern.match(/⟪[^⟫]*⟫/gu) ?? [];
const hasInline = selections.some(s => s.includes('│'));
const hasBare = selections.some(s => !s.includes('│'));
if (hasInline && hasBare) throw new Error('mixed inline and bare selections in one operation');

Try / catch

try {
  applySloppy(content, input, ctx);
} catch (err) {
  if (err instanceof Error && err.message.includes('mixes inline and bare selections')) {
    // normalize all selections to one style and re-send
  } else throw err;
}

Prevention

When it happens

Trigger: An operation contains at least one ⟪old│new⟫ inline selection and at least one bare ⟪text⟫ selection (no │ divider) in the same pattern.

Common situations: An LLM merges two edits into one operation, using inline for one change and bare+rewrite for another; a │ divider is accidentally dropped from one selection, turning it bare and tripping the mix check.

Related errors


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