fix(ai): retry DNS transport failures (#6946)

Fixes #6904
This commit is contained in:
Christian Klotz
2026-07-22 12:26:19 +03:00
committed by GitHub
parent a5afc3f171
commit 33e40c3e15
3 changed files with 42 additions and 0 deletions
+3
View File
@@ -51,6 +51,9 @@ const RETRYABLE_PROVIDER_ERROR_PATTERN = buildProviderErrorPattern([
"connection.?lost",
"other side closed",
"fetch failed",
"getaddrinfo",
"ENOTFOUND",
"EAI_AGAIN",
"upstream.?connect",
"reset before headers",
"socket hang up",
+11
View File
@@ -10,6 +10,8 @@ const nvidiaNIMResourceExhaustedMessage = "ResourceExhausted: Worker local total
const bunFetchSocketClosedMessage =
"The socket connection was closed unexpectedly. For more information, pass `verbose: true` in the second argument to fetch()";
const openAIResponsesEarlyEofMessage = "OpenAI Responses stream ended before a terminal response event";
const wrappedDnsLookupError =
"The pending stream has been canceled (caused by: getaddrinfo ENOTFOUND bedrock-runtime.us-east-1.amazonaws.com)";
describe("provider retry classification", () => {
it("matches explicit provider retry guidance", () => {
@@ -38,6 +40,15 @@ describe("provider retry classification", () => {
).toBe(true);
});
it.each([
wrappedDnsLookupError,
"connect ENOTFOUND api.example.com",
"EAI_AGAIN api.example.com",
"getaddrinfo failed for api.example.com",
])("matches DNS transport failure wording: %s", (errorMessage) => {
expect(isRetryableAssistantError(fauxAssistantMessage("", { stopReason: "error", errorMessage }))).toBe(true);
});
it("matches OpenAI Responses streams that end before terminal events", () => {
expect(
isRetryableAssistantError(
@@ -0,0 +1,28 @@
import { fauxAssistantMessage } from "@earendil-works/pi-ai";
import { describe, expect, it } from "vitest";
import { createHarness } from "../harness.ts";
const wrappedDnsLookupError =
"The pending stream has been canceled (caused by: getaddrinfo ENOTFOUND bedrock-runtime.us-east-1.amazonaws.com)";
describe("issue #6904 DNS transport failure retry", () => {
it("retries a transient DNS lookup failure", async () => {
const harness = await createHarness({ settings: { retry: { enabled: true, maxRetries: 3, baseDelayMs: 1 } } });
try {
harness.setResponses([
fauxAssistantMessage("", { stopReason: "error", errorMessage: wrappedDnsLookupError }),
fauxAssistantMessage("recovered after DNS retry"),
]);
await harness.session.prompt("test");
expect(harness.faux.state.callCount).toBe(2);
expect(harness.eventsOfType("auto_retry_start").map((event) => event.errorMessage)).toEqual([
wrappedDnsLookupError,
]);
expect(harness.eventsOfType("auto_retry_end").map((event) => event.success)).toEqual([true]);
} finally {
harness.cleanup();
}
});
});