can1357/oh-my-pi · error · Error

Operation ${operationNumber} is ambiguous: ${candidates.leng

Error message

Operation ${operationNumber} is ambiguous: ${candidates.length} ordered tuples match.

${allRetry}Add context that only the intended match has — one of these:

${retries.join("\n\n")}

What it means

The pattern is not too broad but not unique either: exactly the candidate-count of ordered tuples (more than one) match, so applying the edit would be ambiguous. The library throws, listing per-candidate retry patterns — each enriched with a nearby line only that candidate has — and, when every candidate would get an identical rewrite, a ready-to-use `*` (all matches) operation.

Source

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

		// copies differs only in which blank survives; the seam machinery
		// normalizes that after the pick.
		const normalizedOutcomes = new Set([...outcomes].map(outcome => normalizeText(outcome).text));
		if (normalizedOutcomes.size === 1) return [candidates[0]];
	}
	const retries = candidates.slice(0, 2).map(candidate => {
		const line = lineNumberAt(content, candidate.start);
		const distinguishing = distinguishingContext(content, candidate, candidates);
		const pattern = !distinguishing
			? operation.patternText
			: distinguishing.side === "before"
				? `${distinguishing.line}${GAP}\n${operation.patternText}`
				: `${operation.patternText}\n${GAP}\n${distinguishing.line}`;
		return `Near line ${line}:\n${operationPayload(operation, "", pattern)}`;
	});
	const allRetry = rewriteIsIdenticalForAll(pattern, operation, candidates)
		? `All candidates receive the same rewrite; retry every match:\n${operationPayload(operation, "*")}\n\n`
		: "";
	throw new Error(
		`Operation ${operationNumber} is ambiguous: ${candidates.length} ordered tuples match.\n\n${allRetry}Add context that only the intended match has — one of these:\n\n${retries.join("\n\n")}`,
	);
}

/**
 * A nearby line that only this candidate has — searched above first, then
 * below. Because gaps span freely, an anchor only disambiguates when it does
 * not also sit on the same side of every other candidate. Turns an ambiguous
 * pattern into a unique one for one line plus a gap: cheaper than an ordinal
 * and impossible to misread as an operation index.
 */
function distinguishingContext(
	content: string,
	candidate: Candidate,
	all: Candidate[],
): { side: "before" | "after"; line: string } | undefined {
	const others = all.filter(entry => entry.start !== candidate.start);
	const usable = (line: string | undefined): line is string =>

View on GitHub (pinned to 9690622007)

Solutions

  1. Pick one of the listed per-candidate retry patterns in the error — each adds context unique to that match.
  2. If the rewrite is identical for all candidates and all should change, use the provided `*` (all-occurrences) operation shown in the error.
  3. Extend the pattern span to include the distinguishing identifier (function name, key, unique literal).

Example fix

// before
const pattern = "return null;"; // matches 5 guard clauses
// after
const pattern = "function findUser(id) {\n...\nreturn null;\n}";
Defensive patterns

Strategy: try-catch

Validate before calling

// Count ordered matches of your anchor span before applying; abort if > 1
// unless you intend an all-occurrences (`*`) operation.
const occurrences = countOccurrences(content, patternLiteral);
if (occurrences > 1 && !operation.all) {
  throw new Error(`${occurrences} matches; add distinguishing context or use the * form`);
}

Try / catch

try {
  await applySloppyEdit({ pattern });
} catch (err) {
  if (err instanceof Error && err.message.includes("is ambiguous")) {
    // The error lists per-candidate retry patterns; take the one for the
    // intended match, or if the rewrite is identical everywhere use the
    // provided all-matches (`*`) operation shown in the message.
    return applySloppyEdit({ pattern: pickCandidateRetry(err.message) });
  }
  throw err;
}

Prevention

When it happens

Trigger: An operation whose pattern matches multiple ordered regions, e.g. targeting `return null;` inside a file with many identical guard clauses, or editing one of several near-identical blocks distinguished only by a name outside the pattern span. Thrown when `candidates.length > 1` in the ambiguity check.

Common situations: Editing one occurrence among repeated boilerplate (repeated handler registrations, identical catch blocks, repeated import lines); patterns that omit the distinguishing function/class name; intending 'all matches' but omitting the `*` form.

Related errors


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