diff --git a/CHANGELOG.md b/CHANGELOG.md index 50442bc031a..c9df539bf48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ Docs: https://docs.openclaw.ai - UI/compaction: keep the compaction indicator in a retry-pending state until the run actually finishes, so the UI does not show `Context compacted` before compaction actually finishes. (#55132) Thanks @mpz4life. - Cron/tool schemas: keep cron tool schemas strict-model-friendly while still preserving `failureAlert=false`, nullable `agentId`/`sessionKey`, and flattened add/update recovery for the newly exposed cron job fields. (#55043) Thanks @brunolorente. - BlueBubbles/config: accept `enrichGroupParticipantsFromContacts` in the core strict config schema so gateways no longer fail validation or startup when the BlueBubbles plugin writes that field. (#56889) Thanks @zqchris. +- Agents/failover: classify AbortError and stream-abort messages as timeout so Ollama NDJSON stream aborts stop showing `reason=unknown` in model fallback logs. (#58324) Thanks @yelog ## 2026.4.2 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,