From 66b15d8b8652f480b26e65ac696683d4253ff370 Mon Sep 17 00:00:00 2001 From: Junhyuk Lee Date: Thu, 30 Apr 2026 07:42:51 +0000 Subject: [PATCH 1/2] Advance OSS contribution for OpenAI client intercepts Celery SoftTimeLimitExceeded exception if it happens during API call Nightly Codex produced a focused contribution for https://github.com/openai/openai-python/issues/2737. Constraint: Automated nightly run; keep changes small and reviewable. Confidence: medium Scope-risk: narrow Tested: See uploaded nightly artifacts and workflow logs. Not-tested: Maintainer CI beyond this workflow. --- NIGHTLY_CODEX_FINAL_ATTEMPT_1.md | 12 +++++++ src/openai/_base_client.py | 8 ++--- tests/test_client.py | 58 ++++++++++++++++++++++++++++++-- 3 files changed, 72 insertions(+), 6 deletions(-) create mode 100644 NIGHTLY_CODEX_FINAL_ATTEMPT_1.md diff --git a/NIGHTLY_CODEX_FINAL_ATTEMPT_1.md b/NIGHTLY_CODEX_FINAL_ATTEMPT_1.md new file mode 100644 index 0000000000..cce470eeb8 --- /dev/null +++ b/NIGHTLY_CODEX_FINAL_ATTEMPT_1.md @@ -0,0 +1,12 @@ +Implemented the patch and left changes in the working tree. + +Changed: +- [src/openai/_base_client.py](/home/runner/work/oss-nightly-control/oss-nightly-control/target/src/openai/_base_client.py): retry/wrap only `httpx.RequestError` in sync and async request loops. +- [tests/test_client.py](/home/runner/work/oss-nightly-control/oss-nightly-control/target/tests/test_client.py): updated retry exception tests to use `httpx.ConnectError`; added sync/async regressions proving non-httpx exceptions propagate without retry/wrapping. +- [NIGHTLY_REPORT.md](/home/runner/work/oss-nightly-control/oss-nightly-control/target/NIGHTLY_REPORT.md): required summary, tests, risks. + +Verified: +- `uv run --with-requirements requirements-dev.lock pytest -q tests/test_client.py -k "retries_taken or non_httpx_request_errors_are_not_retried_or_wrapped or retrying_timeout_errors_doesnt_leak"` passed: 22 tests. +- `ruff format` and `ruff check` passed on touched Python files. + +Note: issue [#2737](https://github.com/openai/openai-python/issues/2737) has active PR [#3002](https://github.com/openai/openai-python/pull/3002) with the same core code change; this patch adds the missing focused regression coverage. \ No newline at end of file diff --git a/src/openai/_base_client.py b/src/openai/_base_client.py index a1d0960700..abfb9aff48 100644 --- a/src/openai/_base_client.py +++ b/src/openai/_base_client.py @@ -1038,8 +1038,8 @@ def request( except OpenAIError as err: # Propagate OpenAIErrors as-is, without retrying or wrapping in APIConnectionError raise err - except Exception as err: - log.debug("Encountered Exception", exc_info=True) + except httpx.RequestError as err: + log.debug("Encountered httpx.RequestError", exc_info=True) if remaining_retries > 0: self._sleep_for_retry( @@ -1649,8 +1649,8 @@ async def request( except OpenAIError as err: # Propagate OpenAIErrors as-is, without retrying or wrapping in APIConnectionError raise err - except Exception as err: - log.debug("Encountered Exception", exc_info=True) + except httpx.RequestError as err: + log.debug("Encountered httpx.RequestError", exc_info=True) if remaining_retries > 0: await self._sleep_for_retry( diff --git a/tests/test_client.py b/tests/test_client.py index 570042c46a..a278615685 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -57,6 +57,10 @@ class MockRequestCall(Protocol): request: httpx.Request +class CustomTaskSignal(Exception): + pass + + def _get_params(client: BaseClient[Any, Any]) -> dict[str, str]: request = client._build_request(FinalRequestOptions(method="get", url="/foo")) url = httpx.URL(request.url) @@ -966,7 +970,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: if nb_retries < failures_before_success: nb_retries += 1 if failure_mode == "exception": - raise RuntimeError("oops") + raise httpx.ConnectError("oops") return httpx.Response(500) return httpx.Response(200) @@ -985,6 +989,30 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: assert response.retries_taken == failures_before_success assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success + @pytest.mark.respx(base_url=base_url) + def test_non_httpx_request_errors_are_not_retried_or_wrapped(self, respx_mock: MockRouter, client: OpenAI) -> None: + nb_requests = 0 + + def handler(_request: httpx.Request) -> httpx.Response: + nonlocal nb_requests + nb_requests += 1 + raise CustomTaskSignal("task timeout") + + respx_mock.post("/chat/completions").mock(side_effect=handler) + + with pytest.raises(CustomTaskSignal): + client.with_options(max_retries=4).chat.completions.create( + messages=[ + { + "content": "string", + "role": "developer", + } + ], + model="gpt-5.4", + ) + + assert nb_requests == 1 + @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @mock.patch("openai._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) @@ -2037,7 +2065,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: if nb_retries < failures_before_success: nb_retries += 1 if failure_mode == "exception": - raise RuntimeError("oops") + raise httpx.ConnectError("oops") return httpx.Response(500) return httpx.Response(200) @@ -2056,6 +2084,32 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: assert response.retries_taken == failures_before_success assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success + @pytest.mark.respx(base_url=base_url) + async def test_non_httpx_request_errors_are_not_retried_or_wrapped( + self, respx_mock: MockRouter, async_client: AsyncOpenAI + ) -> None: + nb_requests = 0 + + def handler(_request: httpx.Request) -> httpx.Response: + nonlocal nb_requests + nb_requests += 1 + raise CustomTaskSignal("task timeout") + + respx_mock.post("/chat/completions").mock(side_effect=handler) + + with pytest.raises(CustomTaskSignal): + await async_client.with_options(max_retries=4).chat.completions.create( + messages=[ + { + "content": "string", + "role": "developer", + } + ], + model="gpt-5.4", + ) + + assert nb_requests == 1 + @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @mock.patch("openai._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) From b6b9238b374d20bfc7505247f92fdde277ff36a6 Mon Sep 17 00:00:00 2001 From: Junhyuk Lee <58055473+xodn348@users.noreply.github.com> Date: Thu, 30 Apr 2026 11:45:50 -0500 Subject: [PATCH 2/2] Remove nightly runner artifact from PR branch --- NIGHTLY_CODEX_FINAL_ATTEMPT_1.md | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 NIGHTLY_CODEX_FINAL_ATTEMPT_1.md diff --git a/NIGHTLY_CODEX_FINAL_ATTEMPT_1.md b/NIGHTLY_CODEX_FINAL_ATTEMPT_1.md deleted file mode 100644 index cce470eeb8..0000000000 --- a/NIGHTLY_CODEX_FINAL_ATTEMPT_1.md +++ /dev/null @@ -1,12 +0,0 @@ -Implemented the patch and left changes in the working tree. - -Changed: -- [src/openai/_base_client.py](/home/runner/work/oss-nightly-control/oss-nightly-control/target/src/openai/_base_client.py): retry/wrap only `httpx.RequestError` in sync and async request loops. -- [tests/test_client.py](/home/runner/work/oss-nightly-control/oss-nightly-control/target/tests/test_client.py): updated retry exception tests to use `httpx.ConnectError`; added sync/async regressions proving non-httpx exceptions propagate without retry/wrapping. -- [NIGHTLY_REPORT.md](/home/runner/work/oss-nightly-control/oss-nightly-control/target/NIGHTLY_REPORT.md): required summary, tests, risks. - -Verified: -- `uv run --with-requirements requirements-dev.lock pytest -q tests/test_client.py -k "retries_taken or non_httpx_request_errors_are_not_retried_or_wrapped or retrying_timeout_errors_doesnt_leak"` passed: 22 tests. -- `ruff format` and `ruff check` passed on touched Python files. - -Note: issue [#2737](https://github.com/openai/openai-python/issues/2737) has active PR [#3002](https://github.com/openai/openai-python/pull/3002) with the same core code change; this patch adds the missing focused regression coverage. \ No newline at end of file