{"record":{"id":"006b48f8ae9bf0c8","repo":"can1357/oh-my-pi","slug":"ops-index-pat-must-be-a-non-empty-pattern","errorCode":null,"errorMessage":"`ops[${index}].pat` must be a non-empty pattern","messagePattern":"`ops\\[(.+?)\\]\\.pat` must be a non-empty pattern","errorType":"validation","errorClass":"ToolError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/tools/ast-edit.ts","lineNumber":270,"sourceCode":"\t\t},\n\t];\n\treadonly deferrable = true;\n\treadonly loadMode = \"discoverable\";\n\tconstructor(private readonly session: ToolSession) {\n\t\tthis.description = prompt.render(astEditDescription);\n\t}\n\n\tasync execute(\n\t\t_toolCallId: string,\n\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,","sourceCodeStart":252,"sourceCodeEnd":288,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/tools/ast-edit.ts#L252-L288","documentation":"The ast_edit tool validates each op entry before executing: `pat` (the AST pattern to match) must be a non-empty string. An empty pattern would match nothing or be structurally invalid, so the tool fails fast with a ToolError naming the offending index.","triggerScenarios":"Calling ast_edit with an ops array entry where pat is \"\" — e.g. `{ ops: [{ pat: \"\", out: \"newCode\" }] }`, or a pat built by string interpolation from an empty variable.","commonSituations":"LLM-generated tool arguments with a placeholder pattern left blank; template code where the pattern variable was never populated; copy-paste dropping the pattern.","solutions":["Supply the actual AST pattern string for ops[i].pat.","If patterns are generated dynamically, assert non-empty before invoking the tool.","Check the tool schema (required non-empty pat) and fix the calling prompt/code."],"exampleFix":"// before\nawait astEdit.execute({ path: 'a.ts', ops: [{ pat: '', out: 'foo()' }] });\n// after\nawait astEdit.execute({ path: 'a.ts', ops: [{ pat: 'bar()', out: 'foo()' }] });","handlingStrategy":"validation","validationCode":"ops.forEach((op, i) => {\n  if (typeof op.pat !== 'string' || op.pat.length === 0) {\n    throw new Error(`ops[${i}].pat must be a non-empty string`);\n  }\n});","typeGuard":"function isValidOp(op: unknown): op is { pat: string; out: string } {\n  return typeof op === 'object' && op !== null &&\n    typeof (op as { pat?: unknown }).pat === 'string' && (op as { pat: string }).pat.length > 0 &&\n    typeof (op as { out?: unknown }).out === 'string';\n}","tryCatchPattern":"try {\n  await astEditTool.execute({ path, ops }, signal);\n} catch (err) {\n  if (err instanceof ToolError && err.message.includes('.pat` must be a non-empty')) {\n    // fix ops and retry once\n  }\n}","preventionTips":["Validate ops against the tool schema before dispatching.","Never interpolate an unassigned variable into pat.","In LLM pipelines, reject tool args with empty string fields pre-call."],"tags":["validation","tool-arguments","ast-edit"],"backgroundTag":"empty-required-parameter","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}