{"record":{"id":"5f2cddbe873635e0","repo":"can1357/oh-my-pi","slug":"operation-operationnumber-is-ambiguous-candi","errorCode":null,"errorMessage":"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\")}","messagePattern":"Operation (.+?) is ambiguous: (.+?) ordered tuples match\\.\n\n(.+?)Add context that only the intended match has — one of these:\n\n(.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/edit/sloppy.ts","lineNumber":2562,"sourceCode":"\t\t// copies differs only in which blank survives; the seam machinery\n\t\t// normalizes that after the pick.\n\t\tconst normalizedOutcomes = new Set([...outcomes].map(outcome => normalizeText(outcome).text));\n\t\tif (normalizedOutcomes.size === 1) return [candidates[0]];\n\t}\n\tconst retries = candidates.slice(0, 2).map(candidate => {\n\t\tconst line = lineNumberAt(content, candidate.start);\n\t\tconst distinguishing = distinguishingContext(content, candidate, candidates);\n\t\tconst pattern = !distinguishing\n\t\t\t? operation.patternText\n\t\t\t: distinguishing.side === \"before\"\n\t\t\t\t? `${distinguishing.line}${GAP}\\n${operation.patternText}`\n\t\t\t\t: `${operation.patternText}\\n${GAP}\\n${distinguishing.line}`;\n\t\treturn `Near line ${line}:\\n${operationPayload(operation, \"\", pattern)}`;\n\t});\n\tconst allRetry = rewriteIsIdenticalForAll(pattern, operation, candidates)\n\t\t? `All candidates receive the same rewrite; retry every match:\\n${operationPayload(operation, \"*\")}\\n\\n`\n\t\t: \"\";\n\tthrow new Error(\n\t\t`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\")}`,\n\t);\n}\n\n/**\n * A nearby line that only this candidate has — searched above first, then\n * below. Because gaps span freely, an anchor only disambiguates when it does\n * not also sit on the same side of every other candidate. Turns an ambiguous\n * pattern into a unique one for one line plus a gap: cheaper than an ordinal\n * and impossible to misread as an operation index.\n */\nfunction distinguishingContext(\n\tcontent: string,\n\tcandidate: Candidate,\n\tall: Candidate[],\n): { side: \"before\" | \"after\"; line: string } | undefined {\n\tconst others = all.filter(entry => entry.start !== candidate.start);\n\tconst usable = (line: string | undefined): line is string =>","sourceCodeStart":2544,"sourceCodeEnd":2580,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/edit/sloppy.ts#L2544-L2580","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)."],"exampleFix":"// before\nconst pattern = \"return null;\"; // matches 5 guard clauses\n// after\nconst pattern = \"function findUser(id) {\\n...\\nreturn null;\\n}\";","handlingStrategy":"try-catch","validationCode":"// Count ordered matches of your anchor span before applying; abort if > 1\n// unless you intend an all-occurrences (`*`) operation.\nconst occurrences = countOccurrences(content, patternLiteral);\nif (occurrences > 1 && !operation.all) {\n  throw new Error(`${occurrences} matches; add distinguishing context or use the * form`);\n}","typeGuard":null,"tryCatchPattern":"try {\n  await applySloppyEdit({ pattern });\n} catch (err) {\n  if (err instanceof Error && err.message.includes(\"is ambiguous\")) {\n    // The error lists per-candidate retry patterns; take the one for the\n    // intended match, or if the rewrite is identical everywhere use the\n    // provided all-matches (`*`) operation shown in the message.\n    return applySloppyEdit({ pattern: pickCandidateRetry(err.message) });\n  }\n  throw err;\n}","preventionTips":["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."],"tags":["edit-pattern","matching","ambiguity"],"backgroundTag":"edit-pattern-ambiguous-match","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}