can1357/oh-my-pi · error · ToolError

`ops` must include at least one op entry

Error message

`ops` must include at least one op entry

What it means

ast_edit requires at least one rewrite op. After mapping (and possibly filtering) params.ops into the ops tuples, an empty array means the call would do nothing, so the tool throws ToolError instead of performing a no-op run.

Source

Thrown at packages/coding-agent/src/tools/ast-edit.ts:275

		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,
				cwd: this.session.cwd,
				internalUrlAction: "rewrite",
				settings: this.session.settings,
				signal,
				sessionFile: this.session.getSessionFile() ?? undefined,

View on GitHub (pinned to 9690622007)

Solutions

  1. Include at least one { pat, out } entry in ops.
  2. Skip the ast_edit call entirely in the caller when no rewrites are needed instead of sending an empty list.
  3. Fix argument construction so ops is not dropped/emptied before dispatch.

Example fix

// before
if (ops.length >= 0) await astEdit.execute({ path, ops });
// after
if (ops.length > 0) await astEdit.execute({ path, ops });
Defensive patterns

Strategy: validation

Validate before calling

if (!Array.isArray(ops) || ops.length === 0) {
  throw new Error('ast_edit requires at least one op');
}

Type guard

function hasOps(p: { ops?: unknown }): p is { ops: unknown[] } {
  return Array.isArray(p.ops) && p.ops.length > 0;
}

Prevention

When it happens

Trigger: Calling ast_edit with `ops: []`, or with an ops value that maps to an empty list (e.g. empty params.ops from defaulted/omitted arguments).

Common situations: Generated tool calls where the ops list was omitted or emptied by preprocessing; a caller that conditionally builds ops and invokes the tool even when no rewrites are needed.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/c8f9c8e3617cd284. Report an issue: GitHub.