fix: tighten findSchema.limit from Type.Number to Type.Integer with execution guard (#105931)

* fix: tighten findSchema.limit from Type.Number to Type.Integer with execution guard

Schema-level Type.Integer rejects fractional limits from model tool calls;
the execution guard covers direct execute() callers. Non-finite values
keep the established fallback-to-default behavior so existing callers
are unaffected.

Co-Authored-By: Claude <noreply@anthropic.com>

* test: harden find limit execution coverage

* test: simplify find limit coverage

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Altay <altay@hey.com>
This commit is contained in:
lsr911
2026-07-27 21:17:06 +08:00
committed by GitHub
parent 14d8c2439f
commit 288120e1f6
2 changed files with 19 additions and 15 deletions
+14 -14
View File
@@ -1,5 +1,6 @@
// find tool tests cover custom search operation wiring and result-limit
// normalization for session file discovery.
import { Value } from "typebox/value";
import { describe, expect, it } from "vitest";
import { createFindToolDefinition, type FindOperations } from "./find.js";
@@ -17,7 +18,18 @@ function textContent(
return first?.type === "text" ? (first.text ?? "") : "";
}
function execute(tool: ReturnType<typeof createFindToolDefinition>, limit: number) {
return tool.execute("call-1", { pattern: "*.ts", limit }, undefined, undefined, {} as never);
}
describe("find tool", () => {
it("rejects fractional limits", async () => {
const tool = createFindToolDefinition("/workspace", { operations: operations([]) });
expect(Value.Check(tool.parameters, { pattern: "*.ts", limit: 1.5 })).toBe(false);
await expect(execute(tool, 1.5)).rejects.toThrow("Limit must be an integer");
});
it("clamps non-positive limits before delegating to custom search operations", async () => {
// Clamp before delegation so custom backends never receive a zero/negative
// limit that could make real matches disappear.
@@ -25,13 +37,7 @@ describe("find tool", () => {
operations: operations(["/workspace/a.ts", "/workspace/b.ts"]),
});
const result = await tool.execute(
"call-1",
{ pattern: "*.ts", limit: -4 },
undefined,
undefined,
{} as never,
);
const result = await execute(tool, -4);
expect(textContent(result)).toBe("a.ts\n\n[1 results limit reached]");
expect(result.details?.resultLimitReached).toBe(1);
@@ -42,13 +48,7 @@ describe("find tool", () => {
operations: operations(["/workspace/a.ts", "/workspace/b.ts"]),
});
const result = await tool.execute(
"call-1",
{ pattern: "*.ts", limit: Number.POSITIVE_INFINITY },
undefined,
undefined,
{} as never,
);
const result = await execute(tool, Number.POSITIVE_INFINITY);
expect(textContent(result)).toBe("a.ts\nb.ts");
expect(result.details).toBeUndefined();
+5 -1
View File
@@ -45,7 +45,7 @@ const findSchema = Type.Object({
description: "File glob, e.g. **/*.ts.",
}),
path: Type.Optional(Type.String({ description: "Search dir; default cwd." })),
limit: Type.Optional(Type.Number({ description: "Max results; default 1000." })),
limit: Type.Optional(Type.Integer({ description: "Max results; default 1000." })),
});
const DEFAULT_LIMIT = 1000;
@@ -192,6 +192,10 @@ export function createFindToolDefinition(
void (async () => {
try {
if (Number.isFinite(limit) && !Number.isInteger(limit)) {
settle(() => reject(new Error("Limit must be an integer")));
return;
}
const searchPath = resolveToCwd(searchDir || ".", cwd);
const effectiveLimit = normalizePositiveLimit(limit, DEFAULT_LIMIT);
const ops = customOps ?? defaultFindOperations;