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

kaxil 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 9df6b79fd35 Stop FileSensor deferring when deferrable is not set 
(#73297)
9df6b79fd35 is described below

commit 9df6b79fd35a03ca529c1bb63f2d8485cc654793
Author: Kaxil Naik <[email protected]>
AuthorDate: Fri Sep 18 07:31:42 2026 +0100

    Stop FileSensor deferring when deferrable is not set (#73297)
---
 .../providers/standard/sensors/filesystem.py       |  6 +++++-
 .../tests/unit/standard/sensors/test_filesystem.py | 23 ++++++++++++++++++++++
 2 files changed, 28 insertions(+), 1 deletion(-)

diff --git 
a/providers/standard/src/airflow/providers/standard/sensors/filesystem.py 
b/providers/standard/src/airflow/providers/standard/sensors/filesystem.py
index 307c6d901e8..43a2ddde5f8 100644
--- a/providers/standard/src/airflow/providers/standard/sensors/filesystem.py
+++ b/providers/standard/src/airflow/providers/standard/sensors/filesystem.py
@@ -126,8 +126,12 @@ class FileSensor(BaseSensorOperator):
 
     def execute(self, context: Context) -> None:
         if not self.deferrable:
+            # The sync path blocks in BaseSensorOperator.execute until poke 
succeeds, so the
+            # deferrable branch must not run afterwards: a file consumed 
between the two pokes
+            # would otherwise defer a sensor the caller asked not to defer, 
and on a deployment
+            # with no triggerer the task then sits in ``deferred`` until 
execution_timeout.
             super().execute(context=context)
-        if not self.poke(context=context):
+        elif not self.poke(context=context):
             self.defer(
                 timeout=datetime.timedelta(seconds=self.timeout),
                 trigger=FileTrigger(
diff --git a/providers/standard/tests/unit/standard/sensors/test_filesystem.py 
b/providers/standard/tests/unit/standard/sensors/test_filesystem.py
index 36ab7719a8f..2e59c0c2c34 100644
--- a/providers/standard/tests/unit/standard/sensors/test_filesystem.py
+++ b/providers/standard/tests/unit/standard/sensors/test_filesystem.py
@@ -21,6 +21,7 @@ import os
 import shutil
 import tempfile
 from datetime import timedelta
+from unittest.mock import patch
 
 import pytest
 
@@ -258,3 +259,25 @@ class TestFileSensor:
         assert second.start_trigger_args.trigger_kwargs["filepath"] == 
second.path
         assert first.start_trigger_args.timeout == timedelta(seconds=60)
         assert second.start_trigger_args.timeout == timedelta(seconds=999)
+
+    def test_non_deferrable_sensor_does_not_defer_after_the_sync_path(self):
+        """
+        A sensor the caller did not make deferrable must never defer.
+
+        The sync path pokes until the file appears and then returns. Poking a 
second time
+        afterwards means a file consumed in between defers the task, and on a 
deployment with no
+        triggerer it then sits in ``deferred`` until execution_timeout.
+        """
+        task = FileSensor(
+            task_id="test_no_defer",
+            filepath="temp_dir",
+            fs_conn_id="fs_default",
+            dag=self.dag,
+            timeout=0,
+        )
+
+        # The file is there for the sync path's poke and gone by any second 
one.
+        with patch.object(FileSensor, "poke", side_effect=[True, False]) as 
mock_poke:
+            task.execute({})
+
+        assert mock_poke.call_count == 1, "the sensor poked again after the 
sync path completed"

Reply via email to