fix(agents): mark required-param tool errors as non-retryable (#17533)

* Agents: mark missing tool params as non-retryable

* Agents: include all missing required params in tool errors

* Agents: change required-param errors to retry guidance

* Docs: align changelog text for issue #14729 guidance wording
This commit is contained in:
Tak Hoffman
2026-02-15 15:50:44 -06:00
committed by GitHub
parent 50abdaf33b
commit 0c77851516
3 changed files with 32 additions and 3 deletions

View File

@@ -102,7 +102,10 @@ describe("createOpenClawCodingTools", () => {
execute,
};
const wrapped = __testing.wrapToolParamNormalization(tool, [{ keys: ["path", "file_path"] }]);
const wrapped = __testing.wrapToolParamNormalization(tool, [
{ keys: ["path", "file_path"], label: "path (path or file_path)" },
{ keys: ["content"], label: "content" },
]);
await wrapped.execute("tool-1", { file_path: "foo.txt", content: "x" });
expect(execute).toHaveBeenCalledWith(
@@ -115,9 +118,21 @@ describe("createOpenClawCodingTools", () => {
await expect(wrapped.execute("tool-2", { content: "x" })).rejects.toThrow(
/Missing required parameter/,
);
await expect(wrapped.execute("tool-2", { content: "x" })).rejects.toThrow(
/Supply correct parameters before retrying\./,
);
await expect(wrapped.execute("tool-3", { file_path: " ", content: "x" })).rejects.toThrow(
/Missing required parameter/,
);
await expect(wrapped.execute("tool-3", { file_path: " ", content: "x" })).rejects.toThrow(
/Supply correct parameters before retrying\./,
);
await expect(wrapped.execute("tool-4", {})).rejects.toThrow(
/Missing required parameters: path \(path or file_path\), content/,
);
await expect(wrapped.execute("tool-4", {})).rejects.toThrow(
/Supply correct parameters before retrying\./,
);
});
});

View File

@@ -87,6 +87,12 @@ type RequiredParamGroup = {
label?: string;
};
const RETRY_GUIDANCE_SUFFIX = " Supply correct parameters before retrying.";
function parameterValidationError(message: string): Error {
return new Error(`${message}.${RETRY_GUIDANCE_SUFFIX}`);
}
export const CLAUDE_PARAM_GROUPS = {
read: [{ keys: ["path", "file_path"], label: "path (path or file_path)" }],
write: [
@@ -245,9 +251,10 @@ export function assertRequiredParams(
toolName: string,
): void {
if (!record || typeof record !== "object") {
throw new Error(`Missing parameters for ${toolName}`);
throw parameterValidationError(`Missing parameters for ${toolName}`);
}
const missingLabels: string[] = [];
for (const group of groups) {
const satisfied = group.keys.some((key) => {
if (!(key in record)) {
@@ -265,9 +272,15 @@ export function assertRequiredParams(
if (!satisfied) {
const label = group.label ?? group.keys.join(" or ");
throw new Error(`Missing required parameter: ${label}`);
missingLabels.push(label);
}
}
if (missingLabels.length > 0) {
const joined = missingLabels.join(", ");
const noun = missingLabels.length === 1 ? "parameter" : "parameters";
throw parameterValidationError(`Missing required ${noun}: ${joined}`);
}
}
// Generic wrapper to normalize parameters for any tool