From 68d63f45eb5e25d6a4bf9c72695e148057da87f9 Mon Sep 17 00:00:00 2001 From: Alex Stiff <46964037+alex-stiff@users.noreply.github.com> Date: Wed, 3 Jun 2026 15:05:51 +0100 Subject: [PATCH 1/2] Fix grid view topological sort for cross-group dependencies --- .../src/airflow/serialization/definitions/taskgroup.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/airflow-core/src/airflow/serialization/definitions/taskgroup.py b/airflow-core/src/airflow/serialization/definitions/taskgroup.py index a5d8b730b05a2..7d8c9e5370391 100644 --- a/airflow-core/src/airflow/serialization/definitions/taskgroup.py +++ b/airflow-core/src/airflow/serialization/definitions/taskgroup.py @@ -236,6 +236,11 @@ def _project_child_deps( self, child_idx: int, child: DAGNode, id_to_idx: dict[str, int] ) -> tuple[int, ...]: upstream_ids = child.upstream_task_ids + if isinstance(child, SerializedTaskGroup): + root_upstream_ids = set() + for root_task in child.get_roots(): + root_upstream_ids.update(root_task.upstream_task_ids) + upstream_ids = upstream_ids | root_upstream_ids if not upstream_ids: return () sib_deps: set[int] = set() From cd8af5827cca4a754a0bcf9a5d45f2e67e20d731 Mon Sep 17 00:00:00 2001 From: Alex Stiff <46964037+alex-stiff@users.noreply.github.com> Date: Thu, 18 Jun 2026 13:25:40 +0100 Subject: [PATCH 2/2] Update test_task_group.py --- .../tests/unit/utils/test_task_group.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/airflow-core/tests/unit/utils/test_task_group.py b/airflow-core/tests/unit/utils/test_task_group.py index 2d1458e95fbd1..d3708bbde0a6c 100644 --- a/airflow-core/tests/unit/utils/test_task_group.py +++ b/airflow-core/tests/unit/utils/test_task_group.py @@ -1150,6 +1150,31 @@ def nested_topo(group): ] +def test_topological_sort_serialized_task_level_cross_group_dep(): + """Task-level deps between groups are respected for ordering after serialization. + + When a task inside group_b depends on a task inside group_a, the serialized + topological sort must place group_a before group_b. + """ + with DAG("test_cross_group_task_dep", schedule=None, start_date=DEFAULT_DATE) as dag: + with TaskGroup("stage_b"): + b_start = EmptyOperator(task_id="b_start") + b_end = EmptyOperator(task_id="b_end") + b_start >> b_end + + with TaskGroup("stage_a"): + a_start = EmptyOperator(task_id="a_start") + a_end = EmptyOperator(task_id="a_end") + a_start >> a_end + + b_end >> a_start + + serialized = create_scheduler_dag(dag) + order = [node.node_id for node in serialized.task_group.topological_sort()] + + assert order.index("stage_b") < order.index("stage_a") + + def test_topological_sort_serialized_layered(): """SerializedTaskGroup.topological_sort emits a valid order after DAG round-trip.