From caa9f4e5551302d8475c74a457847d7977d0952a Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 28 Apr 2026 05:06:36 +0000 Subject: [PATCH 1/3] fix: emit specific error message when API rate limit retries are exhausted When HTTP 429 retries are exhausted, replace the generic 'Exhausted available request attempts' message with 'API rate limit exceeded.' and ensure the failure type is transient_error. This gives users a clear signal that the failure is due to rate limiting rather than an opaque internal error. Co-Authored-By: bot_apk --- airbyte_cdk/sources/streams/http/http_client.py | 17 ++++++++++++++++- unit_tests/sources/streams/http/test_http.py | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/airbyte_cdk/sources/streams/http/http_client.py b/airbyte_cdk/sources/streams/http/http_client.py index 8f70b6e352..c1d0eabd67 100644 --- a/airbyte_cdk/sources/streams/http/http_client.py +++ b/airbyte_cdk/sources/streams/http/http_client.py @@ -298,7 +298,22 @@ def _send_with_retry( return response except BaseBackoffException as e: - self._logger.error(f"Retries exhausted with backoff exception.", exc_info=True) + self._logger.error("Retries exhausted with backoff exception.", exc_info=True) + + is_rate_limited = ( + isinstance(e.response, requests.Response) + and e.response.status_code == requests.codes.too_many_requests + ) + + if is_rate_limited: + raise AirbyteTracedException( + internal_message=f"Rate limit retry budget exhausted. Last exception: {e}", + message="API rate limit exceeded.", + failure_type=FailureType.transient_error, + exception=e, + stream_descriptor=StreamDescriptor(name=self._name), + ) + raise AirbyteTracedException( internal_message=f"Exhausted available request attempts. Exception: {e}", message=f"Exhausted available request attempts. Please see logs for more details. Exception: {e}", diff --git a/unit_tests/sources/streams/http/test_http.py b/unit_tests/sources/streams/http/test_http.py index b67d53613f..6efe64d510 100644 --- a/unit_tests/sources/streams/http/test_http.py +++ b/unit_tests/sources/streams/http/test_http.py @@ -316,7 +316,7 @@ def test_raise_on_http_errors_off_429(mocker): mocker.patch.object(requests.Session, "send", return_value=req) with pytest.raises( AirbyteTracedException, - match="Exhausted available request attempts. Please see logs for more details. Exception: HTTP Status Code: 429. Error: Too many requests.", + match="API rate limit exceeded.", ): stream.exit_on_rate_limit = True list(stream.read_records(SyncMode.full_refresh)) From 0d87243dddd8d9eb07c1a475bdc5bcb5367720fa Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 28 Apr 2026 05:16:47 +0000 Subject: [PATCH 2/3] fix: update remaining test asserting old 429 message Co-Authored-By: bot_apk --- unit_tests/sources/streams/http/test_http.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unit_tests/sources/streams/http/test_http.py b/unit_tests/sources/streams/http/test_http.py index 6efe64d510..dfacbbed91 100644 --- a/unit_tests/sources/streams/http/test_http.py +++ b/unit_tests/sources/streams/http/test_http.py @@ -222,7 +222,7 @@ def get_error_handler(self) -> Optional[ErrorHandler]: send_mock = mocker.patch.object(requests.Session, "send", return_value=req) with pytest.raises( - AirbyteTracedException, match="Exception: HTTP Status Code: 429. Error: Too many requests." + AirbyteTracedException, match="API rate limit exceeded.", ): list(stream.read_records(SyncMode.full_refresh)) if retries <= 0: From 7221854aff5e14f98d88a645624ede8e944b6aae Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 28 Apr 2026 05:18:09 +0000 Subject: [PATCH 3/3] style: fix ruff formatting Co-Authored-By: bot_apk --- unit_tests/sources/streams/http/test_http.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/unit_tests/sources/streams/http/test_http.py b/unit_tests/sources/streams/http/test_http.py index dfacbbed91..7f4f9ac412 100644 --- a/unit_tests/sources/streams/http/test_http.py +++ b/unit_tests/sources/streams/http/test_http.py @@ -222,7 +222,8 @@ def get_error_handler(self) -> Optional[ErrorHandler]: send_mock = mocker.patch.object(requests.Session, "send", return_value=req) with pytest.raises( - AirbyteTracedException, match="API rate limit exceeded.", + AirbyteTracedException, + match="API rate limit exceeded.", ): list(stream.read_records(SyncMode.full_refresh)) if retries <= 0: