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
- Pick one of the listed per-candidate retry patterns in the error — each adds context unique to that match.
- If the rewrite is identical for all candidates and all should change, use the provided `*` (all-occurrences) operation shown in the error.
- 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
- Include the distinguishing name (function/class/key) in the pattern span when a file has repeated structures.
- If the edit should apply everywhere, use the explicit all-occurrences (`*`) form from the start.
- When ambiguity hits, use the exact retry patterns the error lists instead of hand-editing.
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
- Operation ${operationNumber} pattern is too broad; add anoth
- Operation ${operationNumber} adds or removes whole lines but
- Operation ${operationNumber} did not match ${path}: your lin
- ${operation.all ? `Operation ${operationNumber} ${OPENER}* f
- Found ${occurrences} occurrences${pathSuffix}${moreMsg}:\n\n
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/5f2cddbe873635e0.
Report an issue: GitHub.