{"record":{"id":"5502adb6318eeb25","repo":"can1357/oh-my-pi","slug":"duplicate-rewrite-pattern-pat","errorCode":null,"errorMessage":"Duplicate rewrite pattern: ${pat}","messagePattern":"Duplicate rewrite pattern: (.+?)","errorType":"validation","errorClass":"ToolError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/tools/ast-edit.ts","lineNumber":280,"sourceCode":"\t\tparams: AstEditSchemaInfer,\n\t\tsignal?: AbortSignal,\n\t\t_onUpdate?: AgentToolUpdateCallback<AstEditToolDetails>,\n\t\t_context?: AgentToolContext,\n\t): Promise<AgentToolResult<AstEditToolDetails>> {\n\t\treturn untilAborted(signal, async () => {\n\t\t\tconst ops = params.ops.map((entry, index) => {\n\t\t\t\tif (entry.pat.length === 0) {\n\t\t\t\t\tthrow new ToolError(`\\`ops[${index}].pat\\` must be a non-empty pattern`);\n\t\t\t\t}\n\t\t\t\treturn [entry.pat, entry.out] as const;\n\t\t\t});\n\t\t\tif (ops.length === 0) {\n\t\t\t\tthrow new ToolError(\"`ops` must include at least one op entry\");\n\t\t\t}\n\t\t\tconst seenPatterns = new Set<string>();\n\t\t\tfor (const [pat] of ops) {\n\t\t\t\tif (seenPatterns.has(pat)) {\n\t\t\t\t\tthrow new ToolError(`Duplicate rewrite pattern: ${pat}`);\n\t\t\t\t}\n\t\t\t\tseenPatterns.add(pat);\n\t\t\t}\n\t\t\tconst normalizedRewrites = Object.fromEntries(ops);\n\t\t\tconst maxFiles = $envpos(\"PI_MAX_AST_FILES\", 1000);\n\n\t\t\tconst scope = await resolveToolSearchScope({\n\t\t\t\trawPaths: params.paths,\n\t\t\t\tcwd: this.session.cwd,\n\t\t\t\tinternalUrlAction: \"rewrite\",\n\t\t\t\tsettings: this.session.settings,\n\t\t\t\tsignal,\n\t\t\t\tsessionFile: this.session.getSessionFile() ?? undefined,\n\t\t\t\tlocalProtocolOptions: this.session.localProtocolOptions,\n\t\t\t\tskills: this.session.skills,\n\t\t\t\tresolveExternalUrl: async rawPath => {\n\t\t\t\t\tif (!parseReadUrlTarget(rawPath)) return undefined;\n\t\t\t\t\tthrow new ToolError(","sourceCodeStart":262,"sourceCodeEnd":298,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/tools/ast-edit.ts#L262-L298","documentation":"ast_edit rejects duplicate patterns: two ops whose pat strings are identical. Since each pattern maps to exactly one replacement via normalizedRewrites (an object keyed by pattern), duplicates are ambiguous or redundant, so the second occurrence throws ToolError with the offending pattern text.","triggerScenarios":"ops contains two entries with the same pat, e.g. [{pat:'x',out:'y'},{pat:'x',out:'z'}] or an exact repeat — typically from model-generated ops lists that repeat a rewrite.","commonSituations":"LLM emitting the same rewrite twice with slightly different outputs; a caller accumulating ops in a loop without deduping; copying op entries when editing a request.","solutions":["Remove the duplicate op, keeping the single intended replacement.","Deduplicate before calling: build a Map keyed by pat (later entries overwrite earlier) and pass Array.from(map.values()).","If both rewrites are genuinely needed for the same pattern, merge them into one combined `out` or split into multiple sequential ast_edit calls."],"exampleFix":"// before\nawait astEdit.execute({ path, ops: [{ pat: 'foo()', out: 'bar()' }, { pat: 'foo()', out: 'baz()' }] });\n// after\nconst deduped = [...new Map(ops.map(o => [o.pat, o])).values()];\nawait astEdit.execute({ path, ops: deduped });","handlingStrategy":"validation","validationCode":"const pats = ops.map(o => o.pat);\nif (new Set(pats).size !== pats.length) {\n  throw new Error('ops contains duplicate patterns');\n}","typeGuard":null,"tryCatchPattern":"try {\n  await astEditTool.execute({ path, ops }, signal);\n} catch (err) {\n  if (err instanceof ToolError && err.message.startsWith('Duplicate rewrite pattern')) {\n    const pat = err.message.slice('Duplicate rewrite pattern: '.length);\n    ops = [...new Map(ops.map(o => [o.pat, o])).values()];\n  }\n}","preventionTips":["Deduplicate ops by pat with a Map before every call.","In generation pipelines, instruct/parse so each pattern appears once.","Unit-test op builders for duplicate emission when merging sources."],"tags":["validation","duplicate","tool-arguments"],"backgroundTag":"duplicate-parameter","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}