From 0fbdbd3f8ab1876ac260c0f08226a400759323b8 Mon Sep 17 00:00:00 2001 From: David Brailovsky Date: Fri, 16 Oct 2026 10:47:57 +0200 Subject: [PATCH] fix(ai): retry node-fetch 'request to ... failed' errors Google Vertex ADC token refresh failures are reported by node-fetch as 'request to failed, reason: ...' and were never auto-retried. fixes #10996 --- packages/ai/CHANGELOG.md | 1 + packages/ai/src/utils/retry.ts | 3 +++ packages/ai/test/retry.test.ts | 8 ++++++++ 3 files changed, 12 insertions(+) diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index 879517f7c..402b30263 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -25,6 +25,7 @@ - Fixed OpenCode Go MiniMax M2.7 failing with "Model does not support this protocol" by using the Anthropic Messages API - Fixed automatic retry not triggering for gateway `Unable to forward upstream stream` errors ([#10920](https://github.com/earendil-works/pi/issues/10920)) - Fixed automatic retry not triggering for `The service is temporarily unavailable` errors, which also made compaction fail on the first attempt ([#10965](https://github.com/earendil-works/pi/issues/10965)) +- Fixed automatic retry not triggering when the Google Vertex OAuth token refresh fails with a transient network error such as `request to https://oauth2.googleapis.com/token failed, reason:` ([#10996](https://github.com/earendil-works/pi/issues/10996)) - Fixed OpenAI-compatible responses showing thinking after the answer when a proxy such as LiteLLM sends reasoning and content in the same stream chunk ([#10926](https://github.com/earendil-works/pi/issues/10926)) - Fixed Gemini 4 Argon requests on Google Vertex failing with "Thinking budget is not supported", and its thinking level being ignored on the Gemini API, by sending `thinkingLevel` for Gemini 4 and later models ([#10890](https://github.com/earendil-works/pi/issues/10890)) - Fixed Xiaomi MiMo models failing with a 400 at the `minimal` thinking level by sending only the documented thinking on/off toggle and exposing only the `off` and `high` levels ([#10818](https://github.com/earendil-works/pi/issues/10818)) diff --git a/packages/ai/src/utils/retry.ts b/packages/ai/src/utils/retry.ts index c32b07d00..f0b349310 100644 --- a/packages/ai/src/utils/retry.ts +++ b/packages/ai/src/utils/retry.ts @@ -66,6 +66,9 @@ const RETRYABLE_PROVIDER_ERROR_PATTERN = buildProviderErrorPattern([ "connection.?lost", "other side closed", "fetch failed", + // node-fetch system errors, e.g. the Google ADC OAuth token refresh used by + // google-vertex. The reason is often empty (#10996). + "request to \\S+ failed, reason:", "getaddrinfo", "ENOTFOUND", "EAI_AGAIN", diff --git a/packages/ai/test/retry.test.ts b/packages/ai/test/retry.test.ts index 782c0a710..3b2be23c1 100644 --- a/packages/ai/test/retry.test.ts +++ b/packages/ai/test/retry.test.ts @@ -79,6 +79,14 @@ describe("provider retry classification", () => { expect(isRetryableAssistantError(fauxAssistantMessage("", { stopReason: "error", errorMessage }))).toBe(true); }); + it.each([ + // Regression for #10996: node-fetch errors from the google-vertex OAuth token refresh. + "request to https://oauth2.googleapis.com/token failed, reason:", + "request to https://oauth2.googleapis.com/token failed, reason: connect ECONNREFUSED 127.0.0.1:9", + ])("matches node-fetch request failure wording: %s", (errorMessage) => { + expect(isRetryableAssistantError(fauxAssistantMessage("", { stopReason: "error", errorMessage }))).toBe(true); + }); + it("matches OpenAI Responses streams that end before terminal events", () => { expect( isRetryableAssistantError(