fix(failover): classify z.ai network_error stop reason as retryable timeout (#43884)
Merged via squash. Prepared head SHA: 9660f6cd5bcb8d073fc5575bbba2bf3792b29de3 Co-authored-by: hougangdev <105773686+hougangdev@users.noreply.github.com> Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com> Reviewed-by: @altaywtf
This commit is contained in:
@@ -54,6 +54,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Doctor/gateway service audit: earlier groundwork for this fix landed in the superseded #28338 branch. Thanks @realriphub.
|
||||
- Gateway/session stores: regenerate the Swift push-test protocol models and align Windows native session-store realpath handling so protocol checks and sync session discovery stop drifting on Windows. (#44266) thanks @jalehman.
|
||||
- Context engine/session routing: forward optional `sessionKey` through context-engine lifecycle calls so plugins can see structured routing metadata during bootstrap, assembly, post-turn ingestion, and compaction. (#44157) thanks @jalehman.
|
||||
- Agents/failover: classify z.ai `network_error` stop reasons as retryable timeouts so provider connectivity failures trigger fallback instead of surfacing raw unhandled-stop-reason errors. (#43884) Thanks @hougangdev.
|
||||
|
||||
## 2026.3.11
|
||||
|
||||
|
||||
@@ -289,6 +289,9 @@ describe("failover-error", () => {
|
||||
expect(resolveFailoverReasonFromError({ message: "stop reason: error" })).toBe("timeout");
|
||||
expect(resolveFailoverReasonFromError({ message: "reason: abort" })).toBe("timeout");
|
||||
expect(resolveFailoverReasonFromError({ message: "reason: error" })).toBe("timeout");
|
||||
expect(
|
||||
resolveFailoverReasonFromError({ message: "Unhandled stop reason: network_error" }),
|
||||
).toBe("timeout");
|
||||
});
|
||||
|
||||
it("infers timeout from connection/network error messages", () => {
|
||||
|
||||
@@ -576,6 +576,19 @@ describe("isFailoverErrorMessage", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("matches z.ai network_error stop reason as timeout", () => {
|
||||
const samples = [
|
||||
"Unhandled stop reason: network_error",
|
||||
"stop reason: network_error",
|
||||
"reason: network_error",
|
||||
];
|
||||
for (const sample of samples) {
|
||||
expect(isTimeoutErrorMessage(sample)).toBe(true);
|
||||
expect(classifyFailoverReason(sample)).toBe("timeout");
|
||||
expect(isFailoverErrorMessage(sample)).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it("does not classify MALFORMED_FUNCTION_CALL as timeout", () => {
|
||||
const sample = "Unhandled stop reason: MALFORMED_FUNCTION_CALL";
|
||||
expect(isTimeoutErrorMessage(sample)).toBe(false);
|
||||
|
||||
@@ -47,9 +47,9 @@ const ERROR_PATTERNS = {
|
||||
/\benotfound\b/i,
|
||||
/\beai_again\b/i,
|
||||
/without sending (?:any )?chunks?/i,
|
||||
/\bstop reason:\s*(?:abort|error|malformed_response)\b/i,
|
||||
/\breason:\s*(?:abort|error|malformed_response)\b/i,
|
||||
/\bunhandled stop reason:\s*(?:abort|error|malformed_response)\b/i,
|
||||
/\bstop reason:\s*(?:abort|error|malformed_response|network_error)\b/i,
|
||||
/\breason:\s*(?:abort|error|malformed_response|network_error)\b/i,
|
||||
/\bunhandled stop reason:\s*(?:abort|error|malformed_response|network_error)\b/i,
|
||||
],
|
||||
billing: [
|
||||
/["']?(?:status|code)["']?\s*[:=]\s*402\b|\bhttp\s*402\b|\berror(?:\s+code)?\s*[:=]?\s*402\b|\b(?:got|returned|received)\s+(?:a\s+)?402\b|^\s*402\s+payment/i,
|
||||
|
||||
Reference in New Issue
Block a user