Do not retry an HTTP 408; lock retry cap and timeout values
CI / ci (push) Successful in 1m30s
Independent review Codex gpt-6-sol high: APPROVED (0 findings)
CI / ci (pull_request) Successful in 1m30s

The OpenAI and Anthropic adapters now report an HTTP 408 as
llm.TimeoutStatusError, which still matches llm.ErrTimeout but carries
its status. Retrying no longer retries any error carrying a 4xx status,
so a vendor's 408 is returned at once while a transport timeout, which
has no status, is still retried.

Tests: 408 (bare and wrapped) is not retried and a transport timeout is;
FromSettings integration cases for an OpenAI and Anthropic 408 and an
OpenAI transport timeout; the cap and default-timeout tests assert the
literal 20 s and 60 s instead of the constants they lock.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-04 10:58:59 -04:00
co-authored by Claude Opus 5.5
parent d90886ddc2
commit f8e975e284
7 changed files with 133 additions and 15 deletions
+14
View File
@@ -196,3 +196,17 @@ func TestTypedErrorsDoNotKeepSecret(t *testing.T) {
}
}
}
func TestTimeoutStatusError(t *testing.T) {
err := fmt.Errorf("openai: %w", NewTimeoutStatusError(408, "HTTP"))
if !errors.Is(err, ErrTimeout) || errors.Is(err, ErrProvider) {
t.Fatalf("%v: want ErrTimeout only", err)
}
var ts *TimeoutStatusError
if !errors.As(err, &ts) || ts.Status() != 408 {
t.Fatalf("TimeoutStatusError from %v", err)
}
if s := ts.Error() + fmt.Sprintf("%#v", ts); strings.Contains(s, "HTTP") {
t.Fatalf("text holds the secret: %s", s)
}
}