mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 05:16:23 -06:00
fix(agents): honor completed semantic command exits (#115737)
Preserve explicit tool failures while preventing completed grep and semantic shell exits from producing false agent, cron, and provider-runtime failures. Fixes #107278. Co-authored-by: Yuval Dinodia <102706514+yetval@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
eee5e029e3
commit
06c5bd6340
@@ -195,8 +195,9 @@ describe("isToolResultError", () => {
|
||||
expect(isToolResultError({ details: { status: "blocked" } })).toBe(true);
|
||||
expect(isToolResultError({ details: { status: "approval-unavailable" } })).toBe(true);
|
||||
expect(isToolResultError({ details: { status: "completed", timedOut: true } })).toBe(true);
|
||||
expect(isToolResultError({ details: { status: "completed", exitCode: 1 } })).toBe(true);
|
||||
expect(isToolResultError({ details: { status: "completed", exitCode: 1 } })).toBe(false);
|
||||
expect(isToolResultError({ details: { status: "completed", exitCode: 0 } })).toBe(false);
|
||||
expect(isToolResultError({ details: { exitCode: 1 } })).toBe(true);
|
||||
expect(isToolResultError({ details: { ok: true, status: "cancelled" } })).toBe(false);
|
||||
expect(isToolResultError({ details: { success: true, status: "canceled" } })).toBe(false);
|
||||
expect(isToolResultError({ details: { ok: false, status: "completed" } })).toBe(true);
|
||||
|
||||
@@ -1,9 +1,33 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
isToolResultError,
|
||||
resolveToolExecutionErrorKind,
|
||||
resolveToolResultFailureKind,
|
||||
} from "./tool-result-error.js";
|
||||
|
||||
describe("isToolResultError", () => {
|
||||
it("keeps completed results with nonzero exit codes nonfatal", () => {
|
||||
expect(isToolResultError({ details: { status: "completed", exitCode: 1 } })).toBe(false);
|
||||
expect(isToolResultError({ details: { status: "completed", exitCode: 2 } })).toBe(false);
|
||||
expect(isToolResultError({ details: { status: "completed", exitCode: 0 } })).toBe(false);
|
||||
});
|
||||
|
||||
it("keeps real failures fatal even with a completed status", () => {
|
||||
expect(isToolResultError({ details: { status: "completed", timedOut: true } })).toBe(true);
|
||||
expect(isToolResultError({ details: { status: "completed", error: "spawn failed" } })).toBe(
|
||||
true,
|
||||
);
|
||||
expect(isToolResultError({ details: { ok: false, status: "completed" } })).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps failure statuses and statusless nonzero exits fatal", () => {
|
||||
expect(isToolResultError({ details: { status: "failed", exitCode: 1 } })).toBe(true);
|
||||
expect(isToolResultError({ details: { status: "failed", exitCode: 127 } })).toBe(true);
|
||||
expect(isToolResultError({ details: { status: "killed", exitCode: 137 } })).toBe(true);
|
||||
expect(isToolResultError({ details: { exitCode: 1 } })).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveToolExecutionErrorKind", () => {
|
||||
it("recognizes structured timeout identities", () => {
|
||||
expect(
|
||||
@@ -53,4 +77,13 @@ describe("resolveToolResultFailureKind", () => {
|
||||
expect(resolveToolResultFailureKind({ details: hostileDetails })).toBeUndefined();
|
||||
expect(resolveToolResultFailureKind(hostileResult)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("does not classify completed nonzero exits as failures", () => {
|
||||
expect(
|
||||
resolveToolResultFailureKind({ details: { status: "completed", exitCode: 1 } }),
|
||||
).toBeUndefined();
|
||||
expect(resolveToolResultFailureKind({ details: { status: "failed", exitCode: 1 } })).toBe(
|
||||
"failed",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -108,6 +108,9 @@ export function isToolResultError(result: unknown): boolean {
|
||||
if (timedOut === true || Boolean(error)) {
|
||||
return true;
|
||||
}
|
||||
if (normalized === "completed") {
|
||||
return false;
|
||||
}
|
||||
const exitCode = details ? readToolErrorField(details, "exitCode") : undefined;
|
||||
return typeof exitCode === "number" && Number.isFinite(exitCode) && exitCode !== 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user