diff --git a/products/warehouse_sources/backend/temporal/data_imports/sources/common/rest_source/rest_client.py b/products/warehouse_sources/backend/temporal/data_imports/sources/common/rest_source/rest_client.py index e5f92e28f622..a93bd7f3dbc3 100644 --- a/products/warehouse_sources/backend/temporal/data_imports/sources/common/rest_source/rest_client.py +++ b/products/warehouse_sources/backend/temporal/data_imports/sources/common/rest_source/rest_client.py @@ -30,7 +30,14 @@ logger = logging.getLogger(__name__) -class RESTClientRetryableError(Exception): +class RESTClientRetryableError(NonReportableError): + """A transient failure (429/5xx, connection reset/timeout, malformed/truncated body) that + tenacity reissues up to the client's attempt cap. Once that budget is exhausted it escapes to + the activity and Temporal's own activity retry takes over — the upstream blip is expected to + clear, not a PostHog defect. Subclasses NonReportableError, like ``RESTClientNonRetryableError``, + so the activity interceptor keeps it out of error tracking instead of minting an issue per blip. + """ + def __init__(self, message: str, retry_after: Optional[float] = None) -> None: super().__init__(message) self.retry_after = retry_after diff --git a/products/warehouse_sources/backend/temporal/data_imports/sources/common/rest_source/tests/test_rest_client.py b/products/warehouse_sources/backend/temporal/data_imports/sources/common/rest_source/tests/test_rest_client.py index 4b1126306711..b75c81e62014 100644 --- a/products/warehouse_sources/backend/temporal/data_imports/sources/common/rest_source/tests/test_rest_client.py +++ b/products/warehouse_sources/backend/temporal/data_imports/sources/common/rest_source/tests/test_rest_client.py @@ -353,10 +353,14 @@ def test_send_request_raises_retryable_after_persistent_transient_error( mock_session.send.return_value = error client = RESTClient(base_url="https://api.example.com") - with pytest.raises(RESTClientRetryableError): + with pytest.raises(RESTClientRetryableError) as ctx: list(client.paginate(path="/items", paginator=SinglePagePaginator())) assert mock_session.send.call_count == 5 + # An upstream blip surviving every tenacity attempt is expected to clear on Temporal's own + # activity retry, not a PostHog defect, so it must carry the non-reportable marker the + # activity interceptor uses to keep it out of error tracking. + assert isinstance(ctx.value, NonReportableError) @pytest.mark.parametrize( "content",