202201142 opened a new issue, #69543:
URL: https://github.com/apache/airflow/issues/69543

   ### Under which category would you file this issue?
   
   Providers
   
   ### Apache Airflow version
   
   3.2.2
   
   ### What happened and how to reproduce it?
   
   TimeSensor with start_from_trigger=True produces a different serialized DAG 
(new DAG version) on every DAG-file parse, even when nothing in the DAG file 
has changed.
   Root cause: in 
[TimeSensor.__init__](https://github.com/apache/airflow/blob/main/providers/standard/src/airflow/providers/standard/sensors/time.py#L83),
 the target datetime is computed using datetime.datetime.now(self.dag.timezone):
   ```
   aware_time = timezone.coerce_datetime(
       datetime.datetime.combine(
           datetime.datetime.now(self.dag.timezone), target_time, 
self.dag.timezone
       )
   )
   self.target_datetime = timezone.convert_to_utc(aware_time)
   ...
   if self.start_from_trigger:
       self.start_trigger_args.trigger_kwargs = dict(
           moment=self.target_datetime, end_from_trigger=self.end_from_trigger
       )
   ```
   __init__ runs at DAG-parse time, not task-execution time, so datetime.now() 
is evaluated fresh on every parse cycle. The resulting target_datetime is 
written into start_trigger_args.trigger_kwargs, which is exactly the attribute 
the scheduler serializes to hand the task directly to the triggerer (the reason 
start_from_trigger exists at all). Since a volatile value is now part of the 
serialized operator, the serialized DAG hash changes on essentially every parse 
a new DAG version is created continuously on every dag_run.
   
   Reproducable Example:
   ```
   from __future__ import annotations
   
   import datetime
   import pendulum
   
   from airflow.sdk import DAG
   from airflow.providers.standard.sensors.time import TimeSensor
   
   with DAG(
       dag_id="time_sensor_dag",
       schedule="@daily",
       start_date=pendulum.datetime(2026, 1, 1, tz="UTC"),
       catchup=False,
   ) as dag:
       wait = TimeSensor(
           task_id="wait_until_noon",
           target_time=datetime.time(12, 0, 0),
           start_from_trigger=True,
       )
   ```
   
   - Deploy this DAG with a triggerer running.
   - it will change its verstion every schduled dag_runs.
   
   Also you can verify  from `serialized_dag` table  entry where its dag_hash 
changes because of that TimeSensor task trigger_start_kwargs changes.
   
   
   ### What you think should happen instead?
   
   The docstring for 
[TimeSensor](https://airflow.apache.org/docs/apache-airflow-providers-standard/stable/sensors/datetime.html#:~:text=Time%20will%20be%20evaluated%20against%20data_interval_end%20if%20present%20for%20the%20Dag%20run%2C%20otherwise%20run_after%20will%20be%20used.)
 currently states: "Time will be evaluated against data_interval_end if present 
for the Dag run, otherwise run_after will be used."
   
   implying the target moment should be derived from the DagRun, a runtime 
concept. The actual code instead uses parse-time datetime.now(), which has no 
relation to data_interval_end/run_after. Either the code should be fixed to 
match the documented (run-relative) behavior, or if its not possible then the 
docstring should be corrected to describe what actually happens with stating 
that dag_version will be changed.
   
   Whatever the target-moment computation ends up being, it should not be baked 
into start_trigger_args.trigger_kwargs at __init__/parse time as an absolute 
value that changes on every parse. Compare with DateTimeSensorAsync, where 
target_time is a template_fields entry resolved via Jinja against the run 
context, and TimeDeltaSensor(Async), which doesn't implement start_from_trigger 
at all — presumably because its target (data_interval_end/run_after + delta) 
isn't knowable at parse time either. TimeSensor appears to be the one sensor 
that eagerly computes a volatile value instead of deferring it.
   
   
   ### Operating System
   
   Linux
   
   ### Deployment
   
   Docker-Compose
   
   ### Apache Airflow Provider(s)
   
   standard
   
   ### Versions of Apache Airflow Providers
   
   apache-airflow-providers-standard (main / current released version as of 
Airflow 3.2.2)
   
   
   ### Official Helm Chart version
   
   Not Applicable
   
   ### Kubernetes Version
   
   Not Applicable
   
   ### Helm Chart configuration
   
   Not Applicable
   
   ### Docker Image customizations
   
   Not Applicable
   
   ### Anything else?
   
   I'd love to help contribute — this would be my first PR.
   I'll dig into this further and discuss before submitting a PR, since it 
touches DAG-serialization/versioning behavior. I'm willing to submit the fix, 
but I'd like guidance on the preferred direction: (a) derive the target moment 
from data_interval_end/run_after at trigger-start time instead of parse time, 
(b) some other approach, or (c) remove start_from_trigger from TimeSensor 
entirely.
   I haven't looked at the triggerer's relevent code yet — I'll dig into it and 
come back with findings.
   
   
   
   ### Are you willing to submit PR?
   
   - [x] Yes I am willing to submit a PR!
   
   ### Code of Conduct
   
   - [x] I agree to follow this project's [Code of 
Conduct](https://github.com/apache/airflow/blob/main/CODE_OF_CONDUCT.md)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to