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]
