Skip to content

Commit c57dccd

Browse files
committed
fix(agents): classify provider upstream_error as fallbackable transient failure
When a provider returns an error payload shaped like {"type":"upstream_error","message":"Upstream request failed"} the failover classifier left it unclassified, so the turn ended with "LLM request failed" and no configured fallback model was attempted. The serverError pattern list already covered "upstream error" and "upstream connect error" but not the "upstream_error" type token or the "Upstream request failed" message phrasing, so these provider-side transient failures fell through to the unclassified path that skips model fallback. Add both patterns to serverError so they classify as a transient server_error (mapped to the timeout failover reason), letting the fallback chain rotate to the next candidate. OpenClaw's own generic "LLM request failed" text is unaffected and stays unclassified to avoid fallback loops on internal failures. Closes #95519
1 parent d1cbe29 commit c57dccd

2 files changed

Lines changed: 32 additions & 0 deletions

File tree

src/agents/embedded-agent-helpers/failover-matches.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,3 +177,33 @@ describe("server error status classification", () => {
177177
expect(isServerErrorMessage("Proxy notice: Status: Internal Server Error")).toBe(false);
178178
});
179179
});
180+
181+
describe("upstream provider transient errors (#95519)", () => {
182+
it("treats the upstream_error type token as a server error", () => {
183+
expect(isServerErrorMessage("upstream_error")).toBe(true);
184+
});
185+
186+
it("treats an 'Upstream request failed' message as a server error", () => {
187+
expect(isServerErrorMessage("Upstream request failed")).toBe(true);
188+
});
189+
190+
it("classifies the raw upstream_error payload as a fallbackable reason", () => {
191+
// Provider returned {type:"upstream_error", message:"Upstream request failed"}.
192+
// It must NOT stay unclassified, otherwise the turn ends without trying the
193+
// configured fallback models. server_error reasons map to the transient
194+
// failover path so the next candidate is attempted.
195+
const raw =
196+
'{"error":{"message":"Upstream request failed","type":"upstream_error","param":"","code":null}}';
197+
expect(classifyFailoverReason(raw)).not.toBeNull();
198+
});
199+
200+
it("classifies a bare 'Upstream request failed' message as fallbackable", () => {
201+
expect(classifyFailoverReason("Upstream request failed")).not.toBeNull();
202+
});
203+
204+
it("does not classify the generic 'LLM request failed' assistant text as fallbackable", () => {
205+
// This is OpenClaw's own GENERIC_ASSISTANT_ERROR_TEXT, not a provider signal.
206+
// Treating it as fallbackable would risk a fallback loop on internal failures.
207+
expect(classifyFailoverReason("LLM request failed")).toBeNull();
208+
});
209+
});

src/agents/embedded-agent-helpers/failover-matches.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,9 @@ const ERROR_PATTERNS = {
119119
"bad gateway",
120120
"gateway timeout",
121121
"upstream error",
122+
"upstream_error",
122123
"upstream connect error",
124+
"upstream request failed",
123125
"connection reset",
124126
// Chinese provider server error messages
125127
"内部错误",

0 commit comments

Comments
 (0)