From c7710238a82b5626f5101ef387f73bf70f790dd4 Mon Sep 17 00:00:00 2001 From: akinolur <126256260+akinolur@users.noreply.github.com> Date: Thu, 20 Aug 2026 11:58:04 +0200 Subject: [PATCH] fix(provider): retry Codex server_error Mid-stream response.failed events from OpenAI carry 'server_error' (underscore), but the transient-error marker list only had 'server error' (space). is_transient_response missed it, so the retry loop gave up on the first attempt instead of using its normal 1s/2s/4s backoff. --- nanobot/providers/base.py | 1 + tests/providers/test_openai_codex_provider.py | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/nanobot/providers/base.py b/nanobot/providers/base.py index 4efd8ddb8..0c6a52978 100644 --- a/nanobot/providers/base.py +++ b/nanobot/providers/base.py @@ -326,6 +326,7 @@ class LLMProvider(ABC): "timed out", "connection", "server error", + "server_error", "temporarily unavailable", "速率限制", "访问量过大", diff --git a/tests/providers/test_openai_codex_provider.py b/tests/providers/test_openai_codex_provider.py index 56a4b2f79..cccc5fb6e 100644 --- a/tests/providers/test_openai_codex_provider.py +++ b/tests/providers/test_openai_codex_provider.py @@ -319,6 +319,25 @@ async def test_codex_timeout_error_is_typed_and_retryable(monkeypatch) -> None: assert response.error_should_retry is True +@pytest.mark.asyncio +async def test_codex_mid_stream_server_error_is_treated_as_transient(monkeypatch) -> None: + _mock_codex_token(monkeypatch) + + async def fake_request(*args, **kwargs): + raise RuntimeError( + "Response failed: {'type': 'server_error', 'code': 'server_error', " + "'message': 'An error occurred while processing your request.'}" + ) + + monkeypatch.setattr("nanobot.providers.openai_codex_provider._request_codex", fake_request) + + provider = OpenAICodexProvider() + response = await provider.chat([{"role": "user", "content": "hello"}]) + + assert response.finish_reason == "error" + assert provider_base.LLMProvider.is_transient_response(response) is True + + @pytest.mark.asyncio async def test_codex_provider_passes_proxy_to_oauth_and_response_request(monkeypatch) -> None: proxy = "http://127.0.0.1:23458"