From 987e2ff4366da8a8240020ed63f509548befd489 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 18 Jul 2026 01:04:37 +0100 Subject: [PATCH] fix(gateway): preserve provider failure outcomes (#110252) --- .../server-methods/agent-run-dispatch.ts | 8 +++- .../agent.sessions-and-models.test-utils.ts | 37 +++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/src/gateway/server-methods/agent-run-dispatch.ts b/src/gateway/server-methods/agent-run-dispatch.ts index a10ec96270fb..80f7d286e782 100644 --- a/src/gateway/server-methods/agent-run-dispatch.ts +++ b/src/gateway/server-methods/agent-run-dispatch.ts @@ -224,8 +224,12 @@ export function dispatchAgentRunFromGateway(params: { .catch(async (err: unknown) => { const aborted = isGatewayAgentAbortRejection(err, params.abortController.signal); const renderedErr = formatForLog(err); - const stopReason = resolveGatewayAgentAbortStopReason(params.abortController.signal); - const timeoutPhase = aborted ? resolveGatewayAgentAbortTimeoutPhase(stopReason) : undefined; + const stopReason = aborted + ? resolveGatewayAgentAbortStopReason(params.abortController.signal) + : undefined; + const timeoutPhase = stopReason + ? resolveGatewayAgentAbortTimeoutPhase(stopReason) + : undefined; if (taskTracked) { tryFinalizeTrackedAgentTask({ runId: params.runId, diff --git a/src/gateway/server-methods/agent.sessions-and-models.test-utils.ts b/src/gateway/server-methods/agent.sessions-and-models.test-utils.ts index eb2c1810b0ba..db5ca2bcf7fe 100644 --- a/src/gateway/server-methods/agent.sessions-and-models.test-utils.ts +++ b/src/gateway/server-methods/agent.sessions-and-models.test-utils.ts @@ -19,6 +19,7 @@ import { setDetachedTaskLifecycleRuntime, } from "../../tasks/task-runtime.test-helpers.js"; import { withTempDir } from "../../test-helpers/temp-dir.js"; +import { dispatchAgentRunFromGateway } from "./agent-run-dispatch.js"; import { applyGatewaySubagentRegistryTestDeps, getAgentTestMocks, @@ -691,6 +692,42 @@ describe("gateway agent handler", () => { }); }); + it("settles ordinary async gateway agent rejections as failed", async () => { + const providerError = new Error("provider request failed"); + mocks.agentCommand.mockRejectedValueOnce(providerError); + const context = makeContext(); + const onSettled = vi.fn(() => true); + const respond = vi.fn(); + + dispatchAgentRunFromGateway({ + ingressOpts: { + message: "background cli task", + sessionKey: "agent:main:main", + allowModelOverride: false, + }, + runId: "agent-run-provider-error-settlement", + dedupeKeys: ["agent:agent-run-provider-error-settlement"], + abortController: new AbortController(), + cleanupAbortController: vi.fn(), + respond, + context, + taskTrackingMode: "none", + onSettled, + }); + + await waitForAssertion(() => { + expect(onSettled).toHaveBeenCalledWith({ + terminalOutcome: { + reason: "failed", + status: "error", + error: "Error: provider request failed", + }, + onRecovered: expect.any(Function), + }); + expect(respond).toHaveBeenCalled(); + }); + }); + it("does not overwrite operator-cancelled async gateway agent tasks after late completion", async () => { await withTempDir({ prefix: "openclaw-gateway-agent-task-cancelled-" }, async (root) => { useTestStateDir(root);