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

potiuk 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 4572e0b42f5 Fix AirbyteJobSensor marking a cancelled job as success in 
deferrable mode (#69786)
4572e0b42f5 is described below

commit 4572e0b42f5cdf5aa024edb28df6eaf65fdb8e7a
Author: Steve Ahn <[email protected]>
AuthorDate: Fri Jul 31 12:27:55 2026 -0700

    Fix AirbyteJobSensor marking a cancelled job as success in deferrable mode 
(#69786)
---
 generated/known_airflow_exceptions.txt             |  2 +-
 .../airflow/providers/airbyte/sensors/airbyte.py   | 25 ++++++++++------
 .../tests/unit/airbyte/sensors/test_airbyte.py     | 34 ++++++++++++++++++++++
 3 files changed, 51 insertions(+), 10 deletions(-)

diff --git a/generated/known_airflow_exceptions.txt 
b/generated/known_airflow_exceptions.txt
index 53517fa9dcc..b99a61c7807 100644
--- a/generated/known_airflow_exceptions.txt
+++ b/generated/known_airflow_exceptions.txt
@@ -33,7 +33,7 @@ 
devel-common/src/tests_common/test_utils/sftp_system_helpers.py::1
 devel-common/src/tests_common/test_utils/watcher.py::1
 providers/airbyte/src/airflow/providers/airbyte/hooks/airbyte.py::8
 providers/airbyte/src/airflow/providers/airbyte/operators/airbyte.py::3
-providers/airbyte/src/airflow/providers/airbyte/sensors/airbyte.py::6
+providers/airbyte/src/airflow/providers/airbyte/sensors/airbyte.py::5
 
providers/alibaba/src/airflow/providers/alibaba/cloud/hooks/analyticdb_spark.py::6
 providers/alibaba/src/airflow/providers/alibaba/cloud/hooks/oss.py::11
 
providers/alibaba/src/airflow/providers/alibaba/cloud/operators/analyticdb_spark.py::1
diff --git a/providers/airbyte/src/airflow/providers/airbyte/sensors/airbyte.py 
b/providers/airbyte/src/airflow/providers/airbyte/sensors/airbyte.py
index 5c6be4b9c33..5bd92b80e70 100644
--- a/providers/airbyte/src/airflow/providers/airbyte/sensors/airbyte.py
+++ b/providers/airbyte/src/airflow/providers/airbyte/sensors/airbyte.py
@@ -126,15 +126,22 @@ class AirbyteJobSensor(BaseSensorOperator):
                 )
 
     def execute_complete(self, context: Context, event: Any = None) -> None:
-        """
-        Invoke this callback when the trigger fires; return immediately.
+        """Invoke this callback when the trigger fires and fail unless the job 
succeeded."""
+        status = event["status"]
 
-        Relies on trigger to throw an exception, otherwise it assumes 
execution was
-        successful.
-        """
-        if event["status"] == "error":
+        if status == "success":
+            self.log.info("%s completed successfully.", self.task_id)
+            return None
+
+        if status == "error":
             self.log.debug("An error occurred with context: %s", context)
-            raise AirflowException(event["message"])
+            raise RuntimeError(event["message"])
+
+        if status == "cancelled":
+            # A cancelled job must fail the sensor, matching the poke() 
behavior.
+            self.log.debug("Job was cancelled, context: %s", context)
+            raise RuntimeError(event["message"])
 
-        self.log.info("%s completed successfully.", self.task_id)
-        return None
+        # Fail closed: a status this sensor does not model must not reach the 
success path.
+        self.log.debug("Unexpected status %r, context: %s", status, context)
+        raise RuntimeError(event["message"])
diff --git a/providers/airbyte/tests/unit/airbyte/sensors/test_airbyte.py 
b/providers/airbyte/tests/unit/airbyte/sensors/test_airbyte.py
index 69b5d0bffe0..706ff6e7bdb 100644
--- a/providers/airbyte/tests/unit/airbyte/sensors/test_airbyte.py
+++ b/providers/airbyte/tests/unit/airbyte/sensors/test_airbyte.py
@@ -117,3 +117,37 @@ class TestAirbyteJobSensor:
         )
         assert sensor.poke_interval == 10
         assert sensor.timeout == 3600
+
+    @pytest.mark.parametrize(
+        ("status", "message"),
+        [
+            pytest.param("error", "Job run 1 has failed.", id="error"),
+            pytest.param("cancelled", "Job run 1 has been cancelled.", 
id="cancelled"),
+            pytest.param("unmapped_status", "Job run 1 did something 
unexpected.", id="fail-closed"),
+        ],
+    )
+    def test_execute_complete_fails_when_job_did_not_succeed(self, status, 
message):
+        """A deferred sensor must fail on a non-success event, matching the 
poke() behavior."""
+        sensor = AirbyteJobSensor(
+            task_id=self.task_id,
+            airbyte_job_id=self.job_id,
+            airbyte_conn_id=self.airbyte_conn_id,
+            deferrable=True,
+        )
+        with pytest.raises(RuntimeError, match=message):
+            sensor.execute_complete(context={}, event={"status": status, 
"message": message, "job_id": 1})
+
+    def test_execute_complete_succeeds_on_success_event(self):
+        sensor = AirbyteJobSensor(
+            task_id=self.task_id,
+            airbyte_job_id=self.job_id,
+            airbyte_conn_id=self.airbyte_conn_id,
+            deferrable=True,
+        )
+        assert (
+            sensor.execute_complete(
+                context={},
+                event={"status": "success", "message": "Job run 1 has 
completed successfully.", "job_id": 1},
+            )
+            is None
+        )

Reply via email to