This is an automated email from the ASF dual-hosted git repository.

jscheffl pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new d299b68e97e fix: task run exception never catch by Sentry (#65161)
d299b68e97e is described below

commit d299b68e97e1f339ce38cb3aeecdb63580c6fc4b
Author: EpicFail4Ever <[email protected]>
AuthorDate: Sun Jun 7 16:23:10 2026 +0200

    fix: task run exception never catch by Sentry (#65161)
    
    * implement fix sentry error catching
    
    * implement fix sentry error catching
    
    * implement fix sentry error catching
    
    * change deprecated methods and unit tests
    
    * solve conflicts 3.2.1
    
    * add new_scope in mock reset.
    
    ---------
    
    Co-authored-by: bherve <[email protected]>
    Co-authored-by: Jens Scheffler <[email protected]>
---
 .../sdk/execution_time/sentry/configured.py        |  6 ++-
 .../tests/task_sdk/execution_time/test_sentry.py   | 55 ++++++++++++++++++++++
 2 files changed, 60 insertions(+), 1 deletion(-)

diff --git a/task-sdk/src/airflow/sdk/execution_time/sentry/configured.py 
b/task-sdk/src/airflow/sdk/execution_time/sentry/configured.py
index 87dbee6757b..cfecb8bcd7e 100644
--- a/task-sdk/src/airflow/sdk/execution_time/sentry/configured.py
+++ b/task-sdk/src/airflow/sdk/execution_time/sentry/configured.py
@@ -144,10 +144,14 @@ class ConfiguredSentry(NoopSentry):
                 try:
                     self.add_tagging(context["dag_run"], ti)
                     self.add_breadcrumbs(ti)
-                    return run(ti, context, log)
+                    run_return = run(ti, context, log)
                 except Exception as e:
                     sentry_sdk.capture_exception(e)
                     raise
+                _, _, run_error = run_return
+                if run_error:
+                    sentry_sdk.capture_exception(run_error)
+            return run_return
 
         return wrapped_run
 
diff --git a/task-sdk/tests/task_sdk/execution_time/test_sentry.py 
b/task-sdk/tests/task_sdk/execution_time/test_sentry.py
index 066da5d2eaf..a08812fa460 100644
--- a/task-sdk/tests/task_sdk/execution_time/test_sentry.py
+++ b/task-sdk/tests/task_sdk/execution_time/test_sentry.py
@@ -21,6 +21,7 @@ import datetime
 import importlib
 import sys
 import types
+from typing import TYPE_CHECKING
 from unittest import mock
 
 import pytest
@@ -35,6 +36,11 @@ from airflow.sdk.execution_time.task_runner import 
RuntimeTaskInstance
 
 from tests_common.test_utils.config import conf_vars
 
+if TYPE_CHECKING:
+    from structlog.typing import FilteringBoundLogger as Logger
+
+    from airflow.sdk import Context
+
 LOGICAL_DATE = timezone.utcnow()
 SCHEDULE_INTERVAL = datetime.timedelta(days=1)
 DATA_INTERVAL = (LOGICAL_DATE, LOGICAL_DATE + SCHEDULE_INTERVAL)
@@ -121,8 +127,10 @@ class TestSentryHook:
         sentry_sdk = types.ModuleType("sentry_sdk")
         sentry_sdk.init = mock.MagicMock()
         sentry_sdk.integrations = 
mock.Mock(logging=sentry_sdk_integrations_logging)
+        sentry_sdk.new_scope = mock.MagicMock()
         sentry_sdk.get_current_scope = mock.MagicMock()
         sentry_sdk.add_breadcrumb = mock.MagicMock()
+        sentry_sdk.capture_exception = mock.MagicMock()
 
         sys.modules["sentry_sdk"] = sentry_sdk
         sys.modules["sentry_sdk.integrations.logging"] = 
sentry_sdk_integrations_logging
@@ -135,8 +143,10 @@ class TestSentryHook:
         yield
         mock_sentry_sdk.integrations.logging.ignore_logger.reset_mock()
         mock_sentry_sdk.init.reset_mock()
+        mock_sentry_sdk.new_scope.reset_mock()
         mock_sentry_sdk.get_current_scope.reset_mock()
         mock_sentry_sdk.add_breadcrumb.reset_mock()
+        mock_sentry_sdk.capture_exception.reset_mock()
 
     @pytest.fixture
     def sentry(self, mock_sentry_sdk):
@@ -268,3 +278,48 @@ class TestSentryHook:
         sentry_minimum.prepare_to_enrich_errors(executor_integration="")
         assert mock_sentry_sdk.integrations.logging.ignore_logger.mock_calls 
== [mock.call("airflow.task")]
         assert mock_sentry_sdk.init.mock_calls == [mock.call(integrations=[])]
+
+    @pytest.mark.parametrize(
+        ("run_exception_return", "run_raise"),
+        (
+            pytest.param(ValueError("This is Run Exception"), False, 
id="run_with_raise_exception"),
+            pytest.param(None, True, id="run_with_return_exception"),
+            pytest.param(None, False, id="run_without_exception"),
+        ),
+    )
+    def test_sentry_capture_exception(
+        self,
+        mock_supervisor_comms,
+        sentry,
+        mock_sentry_sdk,
+        dag_run,
+        task_instance,
+        run_exception_return,
+        run_raise,
+    ):
+        """
+        Test that sentry_sdk.capture_exception is called on error
+        """
+        mock_supervisor_comms.send.return_value = 
TaskBreadcrumbsResult.model_construct(
+            breadcrumbs=[TASK_DATA],
+        )
+        log = mock.Mock()
+
+        class TestException(Exception): ...
+
+        @sentry.enrich_errors
+        def mocked_run(ti: RuntimeTaskInstance, context: Context, log: Logger):
+            if run_raise:
+                raise TestException("This is Run Exception")
+            return STATE, None, run_exception_return
+
+        if run_raise:
+            with pytest.raises(TestException):
+                mocked_run(task_instance, {"dag_run": dag_run}, log)
+        else:
+            mocked_run(task_instance, {"dag_run": dag_run}, log)
+
+        if run_exception_return is not None or run_raise:
+            mock_sentry_sdk.capture_exception.assert_called()
+        else:
+            mock_sentry_sdk.capture_exception.assert_not_called()

Reply via email to