From 5a6c422c62107475ffd748c9b3e7380714d4a972 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2026 17:30:33 +0000 Subject: [PATCH 1/3] fix: suppress misleading backoff library log in user_defined_backoff_handler The backoff library's default logger reported 'Backing off _send(...) for 0.0s' because user_defined_backoff_handler uses interval=0 (actual sleep happens in the on_backoff callback). This caused confusion and incorrect bug reports. Changes: - Pass logger=None to backoff.on_exception() to suppress the misleading default log message that reports the interval (0s) instead of the actual sleep time - Improve sleep_on_ratelimit log to show HTTP status code and actual retry time: 'Rate limit exceeded (HTTP 429). Retrying in {N} seconds.' - Format elapsed time in log_give_up for consistency Co-Authored-By: bot_apk --- airbyte_cdk/sources/streams/http/rate_limiting.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/airbyte_cdk/sources/streams/http/rate_limiting.py b/airbyte_cdk/sources/streams/http/rate_limiting.py index 926a7ad56a..f6f65ddeb5 100644 --- a/airbyte_cdk/sources/streams/http/rate_limiting.py +++ b/airbyte_cdk/sources/streams/http/rate_limiting.py @@ -106,19 +106,20 @@ def user_defined_backoff_handler( def sleep_on_ratelimit(details: Mapping[str, Any]) -> None: _, exc, _ = sys.exc_info() if isinstance(exc, UserDefinedBackoffException): + retry_after = exc.backoff if exc.response: logger.info( - f"Status code: {exc.response.status_code!r}, Response Content: {exc.response.content!r}" + f"Rate limit exceeded (HTTP {exc.response.status_code}). Retrying in {retry_after} seconds." ) - retry_after = exc.backoff - logger.info(f"Retrying. Sleeping for {retry_after} seconds") + else: + logger.info(f"Rate limit exceeded. Retrying in {retry_after} seconds.") time.sleep(retry_after + 1) # extra second to cover any fractions of second def log_give_up(details: Mapping[str, Any]) -> None: _, exc, _ = sys.exc_info() if isinstance(exc, RequestException): logger.error( - f"Max retry limit reached in {details['elapsed']}s. Request: {exc.request}, Response: {exc.response}" + f"Max retry limit reached after {details['elapsed']:.1f}s. Request: {exc.request}, Response: {exc.response}" ) else: logger.error("Max retry limit reached for unknown request and response") @@ -132,6 +133,7 @@ def log_give_up(details: Mapping[str, Any]) -> None: jitter=None, max_tries=max_tries, max_time=max_time, + logger=None, # suppress the backoff library's default log that misleadingly reports interval (0s) instead of actual sleep time **kwargs, ) From 789ce9dbd40323f67bb93fdc39af4875edc09491 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2026 20:57:01 +0000 Subject: [PATCH 2/3] fix: prepend UserDefinedBackoffException to log messages per reviewer feedback Co-Authored-By: bot_apk --- airbyte_cdk/sources/streams/http/rate_limiting.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/airbyte_cdk/sources/streams/http/rate_limiting.py b/airbyte_cdk/sources/streams/http/rate_limiting.py index f6f65ddeb5..9d1bcc2c8b 100644 --- a/airbyte_cdk/sources/streams/http/rate_limiting.py +++ b/airbyte_cdk/sources/streams/http/rate_limiting.py @@ -109,10 +109,12 @@ def sleep_on_ratelimit(details: Mapping[str, Any]) -> None: retry_after = exc.backoff if exc.response: logger.info( - f"Rate limit exceeded (HTTP {exc.response.status_code}). Retrying in {retry_after} seconds." + f"UserDefinedBackoffException: Rate limit exceeded (HTTP {exc.response.status_code}). Retrying in {retry_after} seconds." ) else: - logger.info(f"Rate limit exceeded. Retrying in {retry_after} seconds.") + logger.info( + f"UserDefinedBackoffException: Rate limit exceeded. Retrying in {retry_after} seconds." + ) time.sleep(retry_after + 1) # extra second to cover any fractions of second def log_give_up(details: Mapping[str, Any]) -> None: From 30132a2bfc9a12f43cac63c77a339ee453caf71b Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2026 21:07:37 +0000 Subject: [PATCH 3/3] =?UTF-8?q?fix:=20address=20Copilot=20review=20?= =?UTF-8?q?=E2=80=94=20Response=20falsey=20check,=20sleep=20duration=20log?= =?UTF-8?q?,=20kwargs=20conflict?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: bot_apk --- airbyte_cdk/sources/streams/http/rate_limiting.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/airbyte_cdk/sources/streams/http/rate_limiting.py b/airbyte_cdk/sources/streams/http/rate_limiting.py index 9d1bcc2c8b..0137aa5043 100644 --- a/airbyte_cdk/sources/streams/http/rate_limiting.py +++ b/airbyte_cdk/sources/streams/http/rate_limiting.py @@ -107,15 +107,16 @@ def sleep_on_ratelimit(details: Mapping[str, Any]) -> None: _, exc, _ = sys.exc_info() if isinstance(exc, UserDefinedBackoffException): retry_after = exc.backoff - if exc.response: + sleep_time = retry_after + 1 # extra second to cover any fractions of second + if exc.response is not None: logger.info( - f"UserDefinedBackoffException: Rate limit exceeded (HTTP {exc.response.status_code}). Retrying in {retry_after} seconds." + f"UserDefinedBackoffException: Rate limit exceeded (HTTP {exc.response.status_code}). Retrying in {sleep_time} seconds." ) else: logger.info( - f"UserDefinedBackoffException: Rate limit exceeded. Retrying in {retry_after} seconds." + f"UserDefinedBackoffException: Rate limit exceeded. Retrying in {sleep_time} seconds." ) - time.sleep(retry_after + 1) # extra second to cover any fractions of second + time.sleep(sleep_time) def log_give_up(details: Mapping[str, Any]) -> None: _, exc, _ = sys.exc_info() @@ -126,6 +127,9 @@ def log_give_up(details: Mapping[str, Any]) -> None: else: logger.error("Max retry limit reached for unknown request and response") + # Suppress the backoff library's default log that misleadingly reports interval (0s) instead of actual sleep time + kwargs.pop("logger", None) + return backoff.on_exception( # type: ignore # Decorator function returns a function with a different signature than the input function, so mypy can't infer the type of the returned function backoff.constant, UserDefinedBackoffException, @@ -135,7 +139,7 @@ def log_give_up(details: Mapping[str, Any]) -> None: jitter=None, max_tries=max_tries, max_time=max_time, - logger=None, # suppress the backoff library's default log that misleadingly reports interval (0s) instead of actual sleep time + logger=None, **kwargs, )