can1357/oh-my-pi · error · ToolError
`ops[${index}].pat` must be a non-empty pattern
Error message
`ops[${index}].pat` must be a non-empty pattern What it means
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.
Source
Thrown at packages/coding-agent/src/tools/ast-edit.ts:270
},
];
readonly deferrable = true;
readonly loadMode = "discoverable";
constructor(private readonly session: ToolSession) {
this.description = prompt.render(astEditDescription);
}
async execute(
_toolCallId: string,
params: AstEditSchemaInfer,
signal?: AbortSignal,
_onUpdate?: AgentToolUpdateCallback<AstEditToolDetails>,
_context?: AgentToolContext,
): Promise<AgentToolResult<AstEditToolDetails>> {
return untilAborted(signal, async () => {
const ops = params.ops.map((entry, index) => {
if (entry.pat.length === 0) {
throw new ToolError(`\`ops[${index}].pat\` must be a non-empty pattern`);
}
return [entry.pat, entry.out] as const;
});
if (ops.length === 0) {
throw new ToolError("`ops` must include at least one op entry");
}
const seenPatterns = new Set<string>();
for (const [pat] of ops) {
if (seenPatterns.has(pat)) {
throw new ToolError(`Duplicate rewrite pattern: ${pat}`);
}
seenPatterns.add(pat);
}
const normalizedRewrites = Object.fromEntries(ops);
const maxFiles = $envpos("PI_MAX_AST_FILES", 1000);
const scope = await resolveToolSearchScope({
rawPaths: params.paths,View on GitHub (pinned to 9690622007)
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.
Example fix
// before
await astEdit.execute({ path: 'a.ts', ops: [{ pat: '', out: 'foo()' }] });
// after
await astEdit.execute({ path: 'a.ts', ops: [{ pat: 'bar()', out: 'foo()' }] }); Defensive patterns
Strategy: validation
Validate before calling
ops.forEach((op, i) => {
if (typeof op.pat !== 'string' || op.pat.length === 0) {
throw new Error(`ops[${i}].pat must be a non-empty string`);
}
}); Type guard
function isValidOp(op: unknown): op is { pat: string; out: string } {
return typeof op === 'object' && op !== null &&
typeof (op as { pat?: unknown }).pat === 'string' && (op as { pat: string }).pat.length > 0 &&
typeof (op as { out?: unknown }).out === 'string';
} Try / catch
try {
await astEditTool.execute({ path, ops }, signal);
} catch (err) {
if (err instanceof ToolError && err.message.includes('.pat` must be a non-empty')) {
// fix ops and retry once
}
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: LLM-generated tool arguments with a placeholder pattern left blank; template code where the pattern variable was never populated; copy-paste dropping the pattern.
Related errors
- rewrite received invalid arguments: ${params.summary}
- approve received invalid arguments: ${params.summary}
- `ops` must include at least one op entry
- Duplicate rewrite pattern: ${pat}
- Cannot rewrite external URL: ${rawPath}. Use `read` or `sear
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/006b48f8ae9bf0c8.
Report an issue: GitHub.