luc-pimentel opened a new issue, #75254:
URL: https://github.com/apache/airflow/issues/75254

   ### Under which category would you file this issue?
   
   Airflow Core
   
   ### Apache Airflow version
   
   3.3.2; also reproduced on main on October 20, 2026.
   
   ### What happened and how to reproduce it?
   
   When a Dag stops deserializing (for example a custom timetable whose 
`deserialize` reads a Variable that was deleted) and one of its tasks is in 
`scheduled` with `max_active_tis_per_dag` or `max_active_tis_per_dagrun` set, 
the scheduler exits with an uncaught `DeserializationError` on its first loop 
after a restart, and again on every restart. Every other Dag stops being 
scheduled until that task is cleared or the Dag loads again. #74499 fixed the 
same crash for a queued run of such a Dag; a running run with a scheduled task 
still has it.
   
   Plugin (`plugins/probe_timetable.py`): a timetable that fails to deserialize 
once a flag file is gone, standing in for one that reads a missing Variable.
   
   ```python
   import os
   
   from airflow.plugins_manager import AirflowPlugin
   from airflow.timetables.base import DataInterval, Timetable
   
   
   class FlagTimetable(Timetable):
       def infer_manual_data_interval(self, *, run_after):
           return DataInterval(run_after, run_after)
   
       def next_dagrun_info(self, *, last_automated_data_interval, restriction):
           return None
   
       def serialize(self):
           return {}
   
       @classmethod
       def deserialize(cls, data):
           if not os.path.exists(os.environ["PROBE_FLAG"]):
               raise ValueError("flag missing, cannot deserialize 
FlagTimetable")
           return cls()
   
   
   class ProbeTimetablePlugin(AirflowPlugin):
       name = "probe_timetable"
       timetables = [FlagTimetable]
   ```
   
   Dag file:
   
   ```python
   from airflow.providers.standard.operators.bash import BashOperator
   from airflow.sdk import DAG
   from probe_timetable import FlagTimetable
   
   with DAG("probe_bad_long", schedule=FlagTimetable(), catchup=False):
       BashOperator(task_id="t1", bash_command="sleep 600", 
max_active_tis_per_dag=1)
   ```
   
   With `PROBE_FLAG` pointing at an existing file and `airflow standalone` 
running (LocalExecutor, SQLite, examples off, Dags unpaused at creation), 
trigger two runs of `probe_bad_long` through the API. Run 1's `t1` starts; run 
2's `t1` stays `scheduled` behind `max_active_tis_per_dag=1`. Then stop 
everything, delete the flag file, and start the scheduler alone:
   
   ```sh
   rm "$PROBE_FLAG"
   airflow scheduler --num-runs 8; echo "exit $?"
   ```
   
   Result on 3.3.2 and main: the scheduler logs "Error scheduling DAG run r2 of 
probe_bad_long" (and r1) with the `ValueError`, and skips those runs as 
expected, then exits with code 1 from `_task_concurrency_allows_execution`:
   
   ```
     File ".../airflow/jobs/scheduler_job_runner.py", line 932, in 
_select_task_instances_to_queue
       self._task_concurrency_allows_execution(
     File ".../airflow/jobs/scheduler_job_runner.py", line 615, in 
_task_concurrency_allows_execution
       serialized_dag = self.scheduler_dag_bag.get_dag_for_run(
     ...
   airflow.exceptions.DeserializationError: An unexpected error occurred while 
trying to deserialize Dag 'probe_bad_long'
   ```
   
   Run 2's `t1` is still `scheduled`, so starting the scheduler again exits the 
same way. With the flag file back, the same command finishes its loops and 
queues `t1`. The same Dag without `max_active_tis_per_dag` only gets the "Error 
scheduling DAG run" log and the scheduler keeps going, because the task 
concurrency check is the only place on this path that loads the Dag again.
   
   ### What you think should happen instead?
   
   Log the error and skip the task, as `_schedule_all_dag_runs` does for the 
run and as #74499 made `_start_queued_dagruns` do for queued runs, so one Dag 
that no longer loads does not stop every other Dag from being scheduled.
   
   In `airflow-core/src/airflow/jobs/scheduler_job_runner.py`, 
`_task_concurrency_allows_execution` calls `scheduler_dag_bag.get_dag_for_run` 
without catching the deserialization error; it is called from 
`_select_task_instances_to_queue` for every scheduled task whose Dag has a task 
concurrency limit, and the error propagates out of the scheduler loop.
   
   ### Operating System
   
   macOS 26.2, Python 3.12 for 3.3.2; Linux/Breeze, Python 3.11 for main.
   
   ### Deployment
   
   Virtualenv installation
   
   ### Versions of Apache Airflow Providers
   
   `apache-airflow-providers-standard==1.19.0`
   
   ### Anything else?
   
   No duplicate found in open/closed issue and PR searches for "scheduler crash 
deserialize", "DeserializationError scheduler", "max_active_tis_per_dag crash", 
"timetable deserialize Variable" and `_task_concurrency_allows_execution`. 
#62050/#72243 are about the same function when the serialized Dag row is 
missing (it returns `None`), not when deserialization raises. #54771 is a 
different deserialization failure.
   
   ### Are you willing to submit PR?
   
   No. I am reporting the bug and am not planning 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).
   
   ---
   Drafted-by: Claude Code 2.1.304 (Claude Fable 5.1); reviewed by 
@luc-pimentel before posting
   


-- 
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