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:
Peter Steinberger
2026-07-29 04:17:51 -04:00
committed by GitHub
parent eee5e029e3
commit 06c5bd6340
3 changed files with 38 additions and 1 deletions
@@ -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);
+33
View File
@@ -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",
);
});
});
+3
View File
@@ -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;
}