From 521fe96a109627e8846dfc752450b8c3679d0153 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Ram=C3=ADrez=20Mondrag=C3=B3n?= Date: Tue, 1 Sep 2026 09:31:04 -0600 Subject: [PATCH 1/2] fix(client): use backoff-provided exception instead of frame introspection python-backoff 3.0 changed its internal call stack, so the frame-walking hack in backoff_handler (a workaround for litl/backoff#158) started raising KeyError: "local variable ''e'' is not defined" and crashing retries. backoff 3.0 already resolved that upstream issue by passing the exception directly via details["exception"], so read it from there instead. --- tap_github/client.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/tap_github/client.py b/tap_github/client.py index cda1aece..40e9726a 100644 --- a/tap_github/client.py +++ b/tap_github/client.py @@ -3,7 +3,6 @@ from __future__ import annotations import email.utils -import inspect import random import time from typing import TYPE_CHECKING, Any, ClassVar, cast @@ -19,7 +18,6 @@ if TYPE_CHECKING: from collections.abc import Iterable - from types import FrameType import requests from backoff.types import Details @@ -330,13 +328,10 @@ def post_process(self, row: dict, context: Context | None = None) -> dict: def backoff_handler(self, details: Details) -> None: """Handle retriable error by swapping auth token.""" self.logger.info("Retrying request with different token") - # use python introspection to obtain the error object - # FIXME: replace this once https://github.com/litl/backoff/issues/158 - # is fixed - exc = cast( - "FrameType", - cast("FrameType", cast("FrameType", inspect.currentframe()).f_back).f_back, - ).f_locals["e"] + # backoff now passes the exception directly in `details`, so we no + # longer need to rely on frame introspection to retrieve it. + # See https://github.com/litl/backoff/issues/158. + exc = cast("RetriableAPIError", details.get("exception")) if ( exc.response is not None and exc.response.status_code == 403 From e40ea78b8931f8739ed4a73a5b21b0f15135b327 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Ram=C3=ADrez=20Mondrag=C3=B3n?= Date: Tue, 1 Sep 2026 09:36:26 -0600 Subject: [PATCH 2/2] fix(client): validate exception type with isinstance instead of cast Avoids an unsafe cast on details["exception"] by narrowing it with isinstance(exc, RetriableAPIError) before accessing .response. --- tap_github/client.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tap_github/client.py b/tap_github/client.py index 40e9726a..fd1142d3 100644 --- a/tap_github/client.py +++ b/tap_github/client.py @@ -331,9 +331,10 @@ def backoff_handler(self, details: Details) -> None: # backoff now passes the exception directly in `details`, so we no # longer need to rely on frame introspection to retrieve it. # See https://github.com/litl/backoff/issues/158. - exc = cast("RetriableAPIError", details.get("exception")) + exc = details.get("exception") if ( - exc.response is not None + isinstance(exc, RetriableAPIError) + and exc.response is not None and exc.response.status_code == 403 and "rate limit exceeded" in str(exc.response.content) ):