diff --git a/src/agents/pi-embedded-helpers.isbillingerrormessage.test.ts b/src/agents/pi-embedded-helpers.isbillingerrormessage.test.ts index eb16f20d49a..dabe78c2d26 100644 --- a/src/agents/pi-embedded-helpers.isbillingerrormessage.test.ts +++ b/src/agents/pi-embedded-helpers.isbillingerrormessage.test.ts @@ -656,6 +656,18 @@ describe("isFailoverErrorMessage", () => { ]); }); + it("matches AbortError / stream-abort messages as timeout (#58315)", () => { + expectTimeoutFailoverSamples([ + "The operation was aborted", + "This operation was aborted", + "the operation was aborted", + "stream closed", + "stream was closed", + "stream aborted", + "stream was aborted", + ]); + }); + it("matches Gemini MALFORMED_RESPONSE stop reason as timeout (#42149)", () => { expectTimeoutFailoverSamples([ "Unhandled stop reason: MALFORMED_RESPONSE", diff --git a/src/agents/pi-embedded-helpers/failover-matches.ts b/src/agents/pi-embedded-helpers/failover-matches.ts index 405a31a81e8..1b3cb262e15 100644 --- a/src/agents/pi-embedded-helpers/failover-matches.ts +++ b/src/agents/pi-embedded-helpers/failover-matches.ts @@ -63,6 +63,11 @@ const ERROR_PATTERNS = { /\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, + // AbortError messages from fetch/stream aborts (Ollama NDJSON stream + // timeouts, signal aborts, etc.) — without these the flattened message + // falls through to reason=unknown (#58315). + /\boperation was aborted\b/i, + /\bstream (?:was )?(?:closed|aborted)\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,