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

jedcunningham 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 3dc4cd2e012 Add run_on_latest_version support for backfill and clear 
operations (#52177)
3dc4cd2e012 is described below

commit 3dc4cd2e01209efbb555b35debeb115367cbeb6d
Author: Ephraim Anierobi <[email protected]>
AuthorDate: Tue Jul 22 18:12:00 2025 +0100

    Add run_on_latest_version support for backfill and clear operations (#52177)
    
    With this option, users are able to choose the dag version they want
    to run their dag/task after clearing or when running backfill. This
    only applies to versioned bundles as non-versioned bundles run with
    the latest dag version.
    
    When the user choose the run with latest version, the bundle_version
    associated with the dagrun is updated to the latest and the associated
    serialized dag version updated to the latest. Choosing not to run
    with latest version which is the default means that the bundle version
    and serialized dag version that the dag ran with initially would be used
    in running it again.
    
    For backfill, there's now --run-on-latest-version flag that makes it run
    with the latest version, otherwise it will run with the original bundle
    the dagrun was created with. Note that it's only useful when rerunning
    a dagrun using backfill. The default behaviour is using the initial 
bundle/version
    and this is intentional otherwise running backfill will fail if there was
    task rename in the latest version.
    
    Summary of changes:
    
    - Use SchedulerDagBag instead of DagBag for execution API
    - Add run_on_latest_version field to DAGRunClearBody and 
ClearTaskInstancesBody models
    - Add --run-on-latest-version CLI flag for backfill command
    - Update backfill.py to support running tasks with latest DAG version
    - Add UI checkbox for "Run with latest version" in clear dialogs
    - Update SchedulerDagBag to handle latest version parameter
    - Update API endpoints to support run_on_latest_version parameter
---
 airflow-core/docs/core-concepts/dag-run.rst        |  2 ++
 airflow-core/src/airflow/api_fastapi/app.py        |  4 ++-
 .../api_fastapi/core_api/datamodels/dag_run.py     |  4 +++
 .../core_api/datamodels/task_instances.py          |  5 +++
 .../core_api/openapi/v2-rest-api-generated.yaml    | 12 +++++++
 .../api_fastapi/core_api/routes/public/dag_run.py  |  2 ++
 .../core_api/routes/public/task_instances.py       |  5 +++
 .../api_fastapi/execution_api/routes/dag_runs.py   | 16 +++++++--
 .../execution_api/routes/task_instances.py         | 25 ++++++++------
 airflow-core/src/airflow/cli/cli_config.py         |  9 +++++
 .../src/airflow/cli/commands/backfill_command.py   |  2 ++
 .../src/airflow/jobs/scheduler_job_runner.py       | 15 +++++----
 airflow-core/src/airflow/models/backfill.py        |  7 +++-
 airflow-core/src/airflow/models/dag.py             |  7 ++++
 airflow-core/src/airflow/models/taskinstance.py    | 10 +++---
 .../airflow/ui/openapi-gen/requests/schemas.gen.ts | 12 +++++++
 .../airflow/ui/openapi-gen/requests/types.gen.ts   |  8 +++++
 .../airflow/ui/public/i18n/locales/en/dags.json    |  1 +
 .../ui/src/components/Clear/Run/ClearRunDialog.tsx | 38 +++++++++++++++++++---
 .../Clear/TaskInstance/ClearTaskInstanceDialog.tsx | 34 +++++++++++++++++--
 .../src/airflow/ui/src/components/ui/index.ts      |  1 +
 .../core_api/routes/public/test_task_instances.py  |  2 +-
 .../versions/v2025_04_28/test_task_instances.py    |  7 ++--
 .../unit/cli/commands/test_backfill_command.py     | 28 ++++++++++++++++
 airflow-core/tests/unit/models/test_backfill.py    | 17 ++++++++--
 airflow-core/tests/unit/models/test_dag.py         | 15 +++++----
 .../src/airflowctl/api/datamodels/generated.py     | 14 ++++++++
 .../tests/unit/common/sql/operators/test_sql.py    |  8 ++++-
 .../tests/unit/standard/decorators/test_bash.py    |  8 ++---
 .../tests/unit/standard/operators/test_python.py   | 13 ++++----
 .../src/airflow/sdk/execution_time/supervisor.py   |  9 ++---
 31 files changed, 276 insertions(+), 64 deletions(-)

diff --git a/airflow-core/docs/core-concepts/dag-run.rst 
b/airflow-core/docs/core-concepts/dag-run.rst
index af7e727de2b..1cb121c9b72 100644
--- a/airflow-core/docs/core-concepts/dag-run.rst
+++ b/airflow-core/docs/core-concepts/dag-run.rst
@@ -167,6 +167,8 @@ the errors after going through the logs, you can re-run the 
tasks by clearing th
 scheduled date. Clearing a task instance creates a record of the task instance.
 The ``try_number`` of the current task instance is incremented, the 
``max_tries`` set to ``0`` and the state set to ``None``, which causes the task 
to re-run.
 
+An experimental feature in Airflow 3.1.0 allows you to clear the task 
instances and re-run with the latest bundle version.
+
 Click on the failed task in the Tree or Graph views and then click on 
**Clear**.
 The executor will re-run it.
 
diff --git a/airflow-core/src/airflow/api_fastapi/app.py 
b/airflow-core/src/airflow/api_fastapi/app.py
index f515be6992c..17de4f677cd 100644
--- a/airflow-core/src/airflow/api_fastapi/app.py
+++ b/airflow-core/src/airflow/api_fastapi/app.py
@@ -84,8 +84,10 @@ def create_app(apps: str = "all") -> FastAPI:
     dag_bag = create_dag_bag()
 
     if "execution" in apps_list or "all" in apps_list:
+        from airflow.jobs.scheduler_job_runner import SchedulerDagBag
+
         task_exec_api_app = create_task_execution_api_app()
-        task_exec_api_app.state.dag_bag = dag_bag
+        task_exec_api_app.state.dag_bag = SchedulerDagBag()
         init_error_handlers(task_exec_api_app)
         app.mount("/execution", task_exec_api_app)
 
diff --git 
a/airflow-core/src/airflow/api_fastapi/core_api/datamodels/dag_run.py 
b/airflow-core/src/airflow/api_fastapi/core_api/datamodels/dag_run.py
index e184a3acfe8..bb79fa671ed 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/datamodels/dag_run.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/datamodels/dag_run.py
@@ -55,6 +55,10 @@ class DAGRunClearBody(StrictBaseModel):
 
     dry_run: bool = True
     only_failed: bool = False
+    run_on_latest_version: bool = Field(
+        default=False,
+        description="(Experimental) Run on the latest bundle version of the 
DAG after clearing the DAG Run.",
+    )
 
 
 class DAGRunResponse(BaseModel):
diff --git 
a/airflow-core/src/airflow/api_fastapi/core_api/datamodels/task_instances.py 
b/airflow-core/src/airflow/api_fastapi/core_api/datamodels/task_instances.py
index e2470119a1c..bb04e829652 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/datamodels/task_instances.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/datamodels/task_instances.py
@@ -179,6 +179,11 @@ class ClearTaskInstancesBody(StrictBaseModel):
     include_downstream: bool = False
     include_future: bool = False
     include_past: bool = False
+    run_on_latest_version: bool = Field(
+        default=False,
+        description="(Experimental) Run on the latest bundle version of the 
DAG after "
+        "clearing the task instances.",
+    )
 
     @model_validator(mode="before")
     @classmethod
diff --git 
a/airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml
 
b/airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml
index 894c306581c..74363c05fe0 100644
--- 
a/airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml
+++ 
b/airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml
@@ -8503,6 +8503,12 @@ components:
           type: boolean
           title: Include Past
           default: false
+        run_on_latest_version:
+          type: boolean
+          title: Run On Latest Version
+          description: (Experimental) Run on the latest bundle version of the 
DAG
+            after clearing the task instances.
+          default: false
       additionalProperties: false
       type: object
       title: ClearTaskInstancesBody
@@ -9140,6 +9146,12 @@ components:
           type: boolean
           title: Only Failed
           default: false
+        run_on_latest_version:
+          type: boolean
+          title: Run On Latest Version
+          description: (Experimental) Run on the latest bundle version of the 
DAG
+            after clearing the DAG Run.
+          default: false
       additionalProperties: false
       type: object
       title: DAGRunClearBody
diff --git 
a/airflow-core/src/airflow/api_fastapi/core_api/routes/public/dag_run.py 
b/airflow-core/src/airflow/api_fastapi/core_api/routes/public/dag_run.py
index 5d974ec012c..00f93754baf 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/routes/public/dag_run.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/routes/public/dag_run.py
@@ -281,6 +281,7 @@ def clear_dag_run(
             run_id=dag_run_id,
             task_ids=None,
             only_failed=body.only_failed,
+            run_on_latest_version=body.run_on_latest_version,
             dry_run=True,
             session=session,
         )
@@ -293,6 +294,7 @@ def clear_dag_run(
         run_id=dag_run_id,
         task_ids=None,
         only_failed=body.only_failed,
+        run_on_latest_version=body.run_on_latest_version,
         session=session,
     )
     dag_run_cleared = session.scalar(select(DagRun).where(DagRun.id == 
dag_run.id))
diff --git 
a/airflow-core/src/airflow/api_fastapi/core_api/routes/public/task_instances.py 
b/airflow-core/src/airflow/api_fastapi/core_api/routes/public/task_instances.py
index 915054cdf79..33bafc0506b 100644
--- 
a/airflow-core/src/airflow/api_fastapi/core_api/routes/public/task_instances.py
+++ 
b/airflow-core/src/airflow/api_fastapi/core_api/routes/public/task_instances.py
@@ -675,7 +675,11 @@ def post_clear_task_instances(
         if dag_run is None:
             error_message = f"Dag Run id {dag_run_id} not found in dag 
{dag_id}"
             raise HTTPException(status.HTTP_404_NOT_FOUND, error_message)
+        # If dag_run_id is provided, we should get the dag from SchedulerDagBag
+        # to ensure we get the right version.
+        from airflow.jobs.scheduler_job_runner import SchedulerDagBag
 
+        dag = SchedulerDagBag().get_dag(dag_run=dag_run, session=session)
         if past or future:
             raise HTTPException(
                 status.HTTP_400_BAD_REQUEST,
@@ -724,6 +728,7 @@ def post_clear_task_instances(
             task_instances,
             session,
             DagRunState.QUEUED if reset_dag_runs else False,
+            run_on_latest_version=body.run_on_latest_version,
         )
 
     return TaskInstanceCollectionResponse(
diff --git 
a/airflow-core/src/airflow/api_fastapi/execution_api/routes/dag_runs.py 
b/airflow-core/src/airflow/api_fastapi/execution_api/routes/dag_runs.py
index db27f7bd93e..04a3e2ce7ec 100644
--- a/airflow-core/src/airflow/api_fastapi/execution_api/routes/dag_runs.py
+++ b/airflow-core/src/airflow/api_fastapi/execution_api/routes/dag_runs.py
@@ -29,7 +29,6 @@ from airflow.api_fastapi.common.types import UtcDateTime
 from airflow.api_fastapi.execution_api.datamodels.dagrun import 
DagRunStateResponse, TriggerDAGRunPayload
 from airflow.exceptions import DagRunAlreadyExists
 from airflow.models.dag import DagModel
-from airflow.models.dagbag import DagBag
 from airflow.models.dagrun import DagRun
 from airflow.utils.types import DagRunTriggeredByType
 
@@ -122,9 +121,20 @@ def clear_dag_run(
                 "message": f"DAG with dag_id: '{dag_id}' has import errors and 
cannot be triggered",
             },
         )
+    from airflow.jobs.scheduler_job_runner import SchedulerDagBag
+
+    dag_run = session.scalar(select(DagRun).where(DagRun.dag_id == dag_id, 
DagRun.run_id == run_id))
+    dag_bag = SchedulerDagBag()
+    dag = dag_bag.get_dag(dag_run=dag_run, session=session)
+    if not dag:
+        raise HTTPException(
+            status.HTTP_404_NOT_FOUND,
+            detail={
+                "reason": "Not Found",
+                "message": f"DAG with dag_id: '{dag_id}' was not found in the 
DagBag",
+            },
+        )
 
-    dag_bag = DagBag(dag_folder=dm.fileloc, read_dags_from_db=True)
-    dag = dag_bag.get_dag(dag_id)
     dag.clear(run_id=run_id)
 
 
diff --git 
a/airflow-core/src/airflow/api_fastapi/execution_api/routes/task_instances.py 
b/airflow-core/src/airflow/api_fastapi/execution_api/routes/task_instances.py
index eef96323d2b..def4dd155c6 100644
--- 
a/airflow-core/src/airflow/api_fastapi/execution_api/routes/task_instances.py
+++ 
b/airflow-core/src/airflow/api_fastapi/execution_api/routes/task_instances.py
@@ -29,7 +29,7 @@ from uuid import UUID
 import attrs
 import structlog
 from cadwyn import VersionedAPIRouter
-from fastapi import Body, HTTPException, Query, status
+from fastapi import Body, Depends, HTTPException, Query, status
 from pydantic import JsonValue
 from sqlalchemy import func, or_, tuple_, update
 from sqlalchemy.exc import NoResultFound, SQLAlchemyError
@@ -37,7 +37,7 @@ from sqlalchemy.orm import joinedload
 from sqlalchemy.sql import select
 from structlog.contextvars import bind_contextvars
 
-from airflow.api_fastapi.common.dagbag import DagBagDep
+from airflow.api_fastapi.common.dagbag import dag_bag_from_app
 from airflow.api_fastapi.common.db.common import SessionDep
 from airflow.api_fastapi.common.types import UtcDateTime
 from airflow.api_fastapi.execution_api.datamodels.taskinstance import (
@@ -76,6 +76,9 @@ if TYPE_CHECKING:
     from airflow.models.expandinput import SchedulerExpandInput
     from airflow.sdk.types import Operator
 
+from airflow.jobs.scheduler_job_runner import SchedulerDagBag
+
+SchedulerDagBagDep = Annotated[SchedulerDagBag, Depends(dag_bag_from_app)]
 
 router = VersionedAPIRouter()
 
@@ -104,7 +107,7 @@ def ti_run(
     task_instance_id: UUID,
     ti_run_payload: Annotated[TIEnterRunningPayload, Body()],
     session: SessionDep,
-    dag_bag: DagBagDep,
+    dag_bag: SchedulerDagBagDep,
 ) -> TIRunContext:
     """
     Run a TaskInstance.
@@ -255,7 +258,7 @@ def ti_run(
             or 0
         )
 
-        if dag := dag_bag.get_dag(ti.dag_id):
+        if dag := dag_bag.get_dag(dag_run=dr, session=session):
             upstream_map_indexes = dict(
                 _get_upstream_map_indexes(dag.get_task(ti.task_id), 
ti.map_index, ti.run_id, session)
             )
@@ -330,7 +333,7 @@ def ti_update_state(
     task_instance_id: UUID,
     ti_patch_payload: Annotated[TIStateUpdate, Body()],
     session: SessionDep,
-    dag_bag: DagBagDep,
+    dag_bag: SchedulerDagBagDep,
 ):
     """
     Update the state of a TaskInstance.
@@ -417,8 +420,9 @@ def ti_update_state(
         )
 
 
-def _handle_fail_fast_for_dag(ti: TI, dag_id: str, session: SessionDep, 
dag_bag: DagBagDep) -> None:
-    ser_dag = dag_bag.get_dag(dag_id)
+def _handle_fail_fast_for_dag(ti: TI, dag_id: str, session: SessionDep, 
dag_bag: SchedulerDagBagDep) -> None:
+    dr = ti.dag_run
+    ser_dag = dag_bag.get_dag(dag_run=dr, session=session)
     if ser_dag and getattr(ser_dag, "fail_fast", False):
         task_dict = getattr(ser_dag, "task_dict")
         task_teardown_map = {k: v.is_teardown for k, v in task_dict.items()}
@@ -432,7 +436,7 @@ def _create_ti_state_update_query_and_update_state(
     query: Update,
     updated_state,
     session: SessionDep,
-    dag_bag: DagBagDep,
+    dag_bag: SchedulerDagBagDep,
     dag_id: str,
 ) -> tuple[Update, TaskInstanceState]:
     if isinstance(ti_patch_payload, (TITerminalStatePayload, 
TIRetryStatePayload, TISuccessStatePayload)):
@@ -893,7 +897,7 @@ def _get_group_tasks(dag_id: str, task_group_id: str, 
session: SessionDep, logic
 def validate_inlets_and_outlets(
     task_instance_id: UUID,
     session: SessionDep,
-    dag_bag: DagBagDep,
+    dag_bag: SchedulerDagBagDep,
 ) -> InactiveAssetsResponse:
     """Validate whether there're inactive assets in inlets and outlets of a 
given task instance."""
     ti_id_str = str(task_instance_id)
@@ -911,7 +915,8 @@ def validate_inlets_and_outlets(
         )
 
     if not ti.task:
-        dag = dag_bag.get_dag(ti.dag_id)
+        dr = ti.dag_run
+        dag = dag_bag.get_dag(dag_run=dr, session=session)
         if dag:
             with contextlib.suppress(TaskNotFound):
                 ti.task = dag.get_task(ti.task_id)
diff --git a/airflow-core/src/airflow/cli/cli_config.py 
b/airflow-core/src/airflow/cli/cli_config.py
index 363839a8580..2006b5f42b9 100644
--- a/airflow-core/src/airflow/cli/cli_config.py
+++ b/airflow-core/src/airflow/cli/cli_config.py
@@ -340,6 +340,14 @@ ARG_BACKFILL_REPROCESS_BEHAVIOR = Arg(
     ),
     choices=("none", "completed", "failed"),
 )
+ARG_BACKFILL_RUN_ON_LATEST_VERSION = Arg(
+    ("--run-on-latest-version",),
+    help=(
+        "(Experimental) If set, the backfill will run tasks using the latest 
bundle version instead of "
+        "the version that was active when the original Dag run was created."
+    ),
+    action="store_true",
+)
 
 
 # misc
@@ -968,6 +976,7 @@ BACKFILL_COMMANDS = (
             ARG_RUN_BACKWARDS,
             ARG_MAX_ACTIVE_RUNS,
             ARG_BACKFILL_REPROCESS_BEHAVIOR,
+            ARG_BACKFILL_RUN_ON_LATEST_VERSION,
             ARG_BACKFILL_DRY_RUN,
         ),
     ),
diff --git a/airflow-core/src/airflow/cli/commands/backfill_command.py 
b/airflow-core/src/airflow/cli/commands/backfill_command.py
index 19108dc4e54..444bc35aea3 100644
--- a/airflow-core/src/airflow/cli/commands/backfill_command.py
+++ b/airflow-core/src/airflow/cli/commands/backfill_command.py
@@ -57,6 +57,7 @@ def create_backfill(args) -> None:
             reverse=args.run_backwards,
             dag_run_conf=args.dag_run_conf,
             reprocess_behavior=reprocess_behavior,
+            run_on_latest_version=args.run_on_latest_version,
         )
         for k, v in params.items():
             console.print(f"    - {k} = {v}")
@@ -88,4 +89,5 @@ def create_backfill(args) -> None:
         dag_run_conf=args.dag_run_conf,
         triggering_user_name=user,
         reprocess_behavior=reprocess_behavior,
+        run_on_latest_version=args.run_on_latest_version,
     )
diff --git a/airflow-core/src/airflow/jobs/scheduler_job_runner.py 
b/airflow-core/src/airflow/jobs/scheduler_job_runner.py
index 4eb99616133..1157c3b51f8 100644
--- a/airflow-core/src/airflow/jobs/scheduler_job_runner.py
+++ b/airflow-core/src/airflow/jobs/scheduler_job_runner.py
@@ -131,15 +131,16 @@ class SchedulerDagBag:
         return dag
 
     @staticmethod
-    def _version_from_dag_run(dag_run, session):
-        if dag_run.bundle_version:
-            dag_version = dag_run.created_dag_version
-        else:
+    def _version_from_dag_run(dag_run, latest, session):
+        if latest or not dag_run.bundle_version:
             dag_version = DagVersion.get_latest_version(dag_id=dag_run.dag_id, 
session=session)
-        return dag_version
+            if dag_version:
+                return dag_version
+
+        return dag_run.created_dag_version
 
-    def get_dag(self, dag_run: DagRun, session: Session) -> DAG | None:
-        version = self._version_from_dag_run(dag_run=dag_run, session=session)
+    def get_dag(self, dag_run: DagRun, session: Session, latest=False) -> DAG 
| None:
+        version = self._version_from_dag_run(dag_run=dag_run, latest=latest, 
session=session)
         if not version:
             return None
         return self._get_dag(version_id=version.id, session=session)
diff --git a/airflow-core/src/airflow/models/backfill.py 
b/airflow-core/src/airflow/models/backfill.py
index e8eb3f8b5cd..e1fd0e506bf 100644
--- a/airflow-core/src/airflow/models/backfill.py
+++ b/airflow-core/src/airflow/models/backfill.py
@@ -291,6 +291,7 @@ def _create_backfill_dag_run(
     dag_run_conf,
     backfill_sort_ordinal,
     triggering_user_name,
+    run_on_latest_version,
     session,
 ):
     from airflow.models.dagrun import DagRun
@@ -328,6 +329,7 @@ def _create_backfill_dag_run(
                     info=info,
                     backfill_id=backfill_id,
                     sort_ordinal=backfill_sort_ordinal,
+                    run_on_latest=run_on_latest_version,
                 )
             else:
                 session.add(
@@ -401,7 +403,7 @@ def _get_info_list(
     return dagrun_info_list
 
 
-def _handle_clear_run(session, dag, dr, info, backfill_id, sort_ordinal):
+def _handle_clear_run(session, dag, dr, info, backfill_id, sort_ordinal, 
run_on_latest=False):
     """Clear the existing DAG run and update backfill metadata."""
     from sqlalchemy.sql import update
 
@@ -415,6 +417,7 @@ def _handle_clear_run(session, dag, dr, info, backfill_id, 
sort_ordinal):
         session=session,
         confirm_prompt=False,
         dry_run=False,
+        run_on_latest_version=run_on_latest,
     )
 
     # Update backfill_id and run_type in DagRun table
@@ -447,6 +450,7 @@ def _create_backfill(
     dag_run_conf: dict | None,
     triggering_user_name: str | None,
     reprocess_behavior: ReprocessBehavior | None = None,
+    run_on_latest_version: bool = False,
 ) -> Backfill | None:
     from airflow.models import DagModel
     from airflow.models.serialized_dag import SerializedDagModel
@@ -510,6 +514,7 @@ def _create_backfill(
                 reprocess_behavior=br.reprocess_behavior,
                 backfill_sort_ordinal=backfill_sort_ordinal,
                 triggering_user_name=br.triggering_user_name,
+                run_on_latest_version=run_on_latest_version,
                 session=session,
             )
             log.info(
diff --git a/airflow-core/src/airflow/models/dag.py 
b/airflow-core/src/airflow/models/dag.py
index a141d96a1c3..43fc62b098d 100644
--- a/airflow-core/src/airflow/models/dag.py
+++ b/airflow-core/src/airflow/models/dag.py
@@ -1282,6 +1282,7 @@ class DAG(TaskSDKDag, LoggingMixin):
         only_running: bool = False,
         confirm_prompt: bool = False,
         dag_run_state: DagRunState = DagRunState.QUEUED,
+        run_on_latest_version: bool = False,
         session: Session = NEW_SESSION,
         dag_bag: DagBag | None = None,
         exclude_task_ids: frozenset[str] | frozenset[tuple[str, int]] | None = 
frozenset(),
@@ -1299,6 +1300,7 @@ class DAG(TaskSDKDag, LoggingMixin):
         confirm_prompt: bool = False,
         dag_run_state: DagRunState = DagRunState.QUEUED,
         dry_run: Literal[False] = False,
+        run_on_latest_version: bool = False,
         session: Session = NEW_SESSION,
         dag_bag: DagBag | None = None,
         exclude_task_ids: frozenset[str] | frozenset[tuple[str, int]] | None = 
frozenset(),
@@ -1317,6 +1319,7 @@ class DAG(TaskSDKDag, LoggingMixin):
         only_running: bool = False,
         confirm_prompt: bool = False,
         dag_run_state: DagRunState = DagRunState.QUEUED,
+        run_on_latest_version: bool = False,
         session: Session = NEW_SESSION,
         dag_bag: DagBag | None = None,
         exclude_task_ids: frozenset[str] | frozenset[tuple[str, int]] | None = 
frozenset(),
@@ -1335,6 +1338,7 @@ class DAG(TaskSDKDag, LoggingMixin):
         confirm_prompt: bool = False,
         dag_run_state: DagRunState = DagRunState.QUEUED,
         dry_run: Literal[False] = False,
+        run_on_latest_version: bool = False,
         session: Session = NEW_SESSION,
         dag_bag: DagBag | None = None,
         exclude_task_ids: frozenset[str] | frozenset[tuple[str, int]] | None = 
frozenset(),
@@ -1354,6 +1358,7 @@ class DAG(TaskSDKDag, LoggingMixin):
         confirm_prompt: bool = False,
         dag_run_state: DagRunState = DagRunState.QUEUED,
         dry_run: bool = False,
+        run_on_latest_version: bool = False,
         session: Session = NEW_SESSION,
         dag_bag: DagBag | None = None,
         exclude_task_ids: frozenset[str] | frozenset[tuple[str, int]] | None = 
frozenset(),
@@ -1372,6 +1377,7 @@ class DAG(TaskSDKDag, LoggingMixin):
         :param dag_run_state: state to set DagRun to. If set to False, dagrun 
state will not
             be changed.
         :param dry_run: Find the tasks to clear but don't clear them.
+        :param run_on_latest_version: whether to run on latest serialized DAG 
and Bundle version
         :param session: The sqlalchemy session to use
         :param dag_bag: The DagBag used to find the dags (Optional)
         :param exclude_task_ids: A set of ``task_id`` or (``task_id``, 
``map_index``)
@@ -1417,6 +1423,7 @@ class DAG(TaskSDKDag, LoggingMixin):
                 list(tis),
                 session,
                 dag_run_state=dag_run_state,
+                run_on_latest_version=run_on_latest_version,
             )
         else:
             count = 0
diff --git a/airflow-core/src/airflow/models/taskinstance.py 
b/airflow-core/src/airflow/models/taskinstance.py
index f4ed5573e59..ca46b480256 100644
--- a/airflow-core/src/airflow/models/taskinstance.py
+++ b/airflow-core/src/airflow/models/taskinstance.py
@@ -203,6 +203,7 @@ def clear_task_instances(
     tis: list[TaskInstance],
     session: Session,
     dag_run_state: DagRunState | Literal[False] = DagRunState.QUEUED,
+    run_on_latest_version: bool = False,
 ) -> None:
     """
     Clear a set of task instances, but make sure the running ones get killed.
@@ -217,6 +218,7 @@ def clear_task_instances(
     :param session: current session
     :param dag_run_state: state to set finished DagRuns to.
         If set to False, DagRuns state will not be changed.
+    :param run_on_latest_version: whether to run on latest serialized DAG and 
Bundle version
 
     :meta private:
     """
@@ -234,7 +236,7 @@ def clear_task_instances(
             ti.state = TaskInstanceState.RESTARTING
         else:
             dr = ti.dag_run
-            ti_dag = scheduler_dagbag.get_dag(dag_run=dr, session=session)
+            ti_dag = scheduler_dagbag.get_dag(dag_run=dr, session=session, 
latest=run_on_latest_version)
             if not ti_dag:
                 log.warning("No serialized dag found for dag '%s'", dr.dag_id)
             task_id = ti.task_id
@@ -277,12 +279,12 @@ def clear_task_instances(
             if dr.state in State.finished_dr_states:
                 dr.state = dag_run_state
                 dr.start_date = timezone.utcnow()
-                dr_dag = scheduler_dagbag.get_dag(dag_run=dr, session=session)
+                dr_dag = scheduler_dagbag.get_dag(dag_run=dr, session=session, 
latest=run_on_latest_version)
                 if not dr_dag:
                     log.warning("No serialized dag found for dag '%s'", 
dr.dag_id)
-                if dr_dag and not dr_dag.disable_bundle_versioning:
+                if dr_dag and not dr_dag.disable_bundle_versioning and 
run_on_latest_version:
                     bundle_version = dr.dag_model.bundle_version
-                    if bundle_version is not None:
+                    if bundle_version is not None and run_on_latest_version:
                         dr.bundle_version = bundle_version
                 if dag_run_state == DagRunState.QUEUED:
                     dr.last_scheduling_decision = None
diff --git a/airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts 
b/airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts
index a91264eb077..2b997947993 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts
@@ -1205,6 +1205,12 @@ export const $ClearTaskInstancesBody = {
             type: 'boolean',
             title: 'Include Past',
             default: false
+        },
+        run_on_latest_version: {
+            type: 'boolean',
+            title: 'Run On Latest Version',
+            description: '(Experimental) Run on the latest bundle version of 
the DAG after clearing the task instances.',
+            default: false
         }
     },
     additionalProperties: false,
@@ -2177,6 +2183,12 @@ export const $DAGRunClearBody = {
             type: 'boolean',
             title: 'Only Failed',
             default: false
+        },
+        run_on_latest_version: {
+            type: 'boolean',
+            title: 'Run On Latest Version',
+            description: '(Experimental) Run on the latest bundle version of 
the DAG after clearing the DAG Run.',
+            default: false
         }
     },
     additionalProperties: false,
diff --git a/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts 
b/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts
index bed40429143..06887e2caa2 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts
@@ -389,6 +389,10 @@ export type ClearTaskInstancesBody = {
     include_downstream?: boolean;
     include_future?: boolean;
     include_past?: boolean;
+    /**
+     * (Experimental) Run on the latest bundle version of the DAG after 
clearing the task instances.
+     */
+    run_on_latest_version?: boolean;
 };
 
 /**
@@ -592,6 +596,10 @@ export type DAGResponse = {
 export type DAGRunClearBody = {
     dry_run?: boolean;
     only_failed?: boolean;
+    /**
+     * (Experimental) Run on the latest bundle version of the DAG after 
clearing the DAG Run.
+     */
+    run_on_latest_version?: boolean;
 };
 
 /**
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/en/dags.json 
b/airflow-core/src/airflow/ui/public/i18n/locales/en/dags.json
index d1ee6a3d0ee..6705e101962 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/en/dags.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/en/dags.json
@@ -62,6 +62,7 @@
       "onlyFailed": "Clear only failed tasks",
       "past": "Past",
       "queueNew": "Queue up new tasks",
+      "runOnLatestVersion": "Run with latest bundle version",
       "upstream": "Upstream"
     }
   },
diff --git 
a/airflow-core/src/airflow/ui/src/components/Clear/Run/ClearRunDialog.tsx 
b/airflow-core/src/airflow/ui/src/components/Clear/Run/ClearRunDialog.tsx
index 696ada0ee38..3b3c9fe5f0e 100644
--- a/airflow-core/src/airflow/ui/src/components/Clear/Run/ClearRunDialog.tsx
+++ b/airflow-core/src/airflow/ui/src/components/Clear/Run/ClearRunDialog.tsx
@@ -21,9 +21,10 @@ import { useState } from "react";
 import { useTranslation } from "react-i18next";
 import { CgRedo } from "react-icons/cg";
 
+import { useDagServiceGetDagDetails } from "openapi/queries";
 import type { DAGRunResponse } from "openapi/requests/types.gen";
 import { ActionAccordion } from "src/components/ActionAccordion";
-import { Button, Dialog } from "src/components/ui";
+import { Button, Dialog, Checkbox } from "src/components/ui";
 import SegmentedControl from "src/components/ui/SegmentedControl";
 import { useClearDagRunDryRun } from "src/queries/useClearDagRunDryRun";
 import { useClearDagRun } from "src/queries/useClearRun";
@@ -43,11 +44,17 @@ const ClearRunDialog = ({ dagRun, onClose, open }: Props) 
=> {
   const [note, setNote] = useState<string | null>(dagRun.note);
   const [selectedOptions, setSelectedOptions] = 
useState<Array<string>>(["existingTasks"]);
   const onlyFailed = selectedOptions.includes("onlyFailed");
+  const [runOnLatestVersion, setRunOnLatestVersion] = useState(false);
+
+  // Get current DAG's bundle version to compare with DAG run's bundle version
+  const { data: dagDetails } = useDagServiceGetDagDetails({
+    dagId,
+  });
 
   const { data: affectedTasks = { task_instances: [], total_entries: 0 } } = 
useClearDagRunDryRun({
     dagId,
     dagRunId,
-    requestBody: { only_failed: onlyFailed },
+    requestBody: { only_failed: onlyFailed, run_on_latest_version: 
runOnLatestVersion },
   });
 
   const { isPending, mutate } = useClearDagRun({
@@ -62,6 +69,13 @@ const ClearRunDialog = ({ dagRun, onClose, open }: Props) => 
{
     onSuccess: onClose,
   });
 
+  // Check if bundle versions are different
+  const currentDagBundleVersion = dagDetails?.bundle_version;
+  const dagRunBundleVersion = dagRun.bundle_version;
+  const bundleVersionsDiffer = currentDagBundleVersion !== dagRunBundleVersion;
+  const shouldShowBundleVersionOption =
+    bundleVersionsDiffer && dagRunBundleVersion !== null && 
dagRunBundleVersion !== "";
+
   return (
     <Dialog.Root lazyMount onOpenChange={onClose} open={open} size="xl">
       <Dialog.Content backdrop>
@@ -101,7 +115,19 @@ const ClearRunDialog = ({ dagRun, onClose, open }: Props) 
=> {
             />
           </Flex>
           <ActionAccordion affectedTasks={affectedTasks} note={note} 
setNote={setNote} />
-          <Flex justifyContent="end" mt={3}>
+          <Flex
+            {...(shouldShowBundleVersionOption ? { alignItems: "center" } : 
{})}
+            justifyContent={shouldShowBundleVersionOption ? "space-between" : 
"end"}
+            mt={3}
+          >
+            {shouldShowBundleVersionOption ? (
+              <Checkbox
+                checked={runOnLatestVersion}
+                onCheckedChange={(event) => 
setRunOnLatestVersion(Boolean(event.checked))}
+              >
+                
{translate("dags:runAndTaskActions.options.runOnLatestVersion")}
+              </Checkbox>
+            ) : undefined}
             <Button
               colorPalette="blue"
               disabled={affectedTasks.total_entries === 0}
@@ -110,7 +136,11 @@ const ClearRunDialog = ({ dagRun, onClose, open }: Props) 
=> {
                 mutate({
                   dagId,
                   dagRunId,
-                  requestBody: { dry_run: false, only_failed: onlyFailed },
+                  requestBody: {
+                    dry_run: false,
+                    only_failed: onlyFailed,
+                    run_on_latest_version: runOnLatestVersion,
+                  },
                 });
                 if (note !== dagRun.note) {
                   mutatePatchDagRun({
diff --git 
a/airflow-core/src/airflow/ui/src/components/Clear/TaskInstance/ClearTaskInstanceDialog.tsx
 
b/airflow-core/src/airflow/ui/src/components/Clear/TaskInstance/ClearTaskInstanceDialog.tsx
index 7458b204898..1f4e1886ea7 100644
--- 
a/airflow-core/src/airflow/ui/src/components/Clear/TaskInstance/ClearTaskInstanceDialog.tsx
+++ 
b/airflow-core/src/airflow/ui/src/components/Clear/TaskInstance/ClearTaskInstanceDialog.tsx
@@ -21,10 +21,11 @@ import { useState } from "react";
 import { useTranslation } from "react-i18next";
 import { CgRedo } from "react-icons/cg";
 
+import { useDagServiceGetDagDetails } from "openapi/queries";
 import type { TaskInstanceResponse } from "openapi/requests/types.gen";
 import { ActionAccordion } from "src/components/ActionAccordion";
 import Time from "src/components/Time";
-import { Button, Dialog } from "src/components/ui";
+import { Button, Dialog, Checkbox } from "src/components/ui";
 import SegmentedControl from "src/components/ui/SegmentedControl";
 import { useClearTaskInstances } from "src/queries/useClearTaskInstances";
 import { useClearTaskInstancesDryRun } from 
"src/queries/useClearTaskInstancesDryRun";
@@ -57,6 +58,7 @@ const ClearTaskInstanceDialog = ({ onClose, open, 
taskInstance }: Props) => {
   const future = selectedOptions.includes("future");
   const upstream = selectedOptions.includes("upstream");
   const downstream = selectedOptions.includes("downstream");
+  const [runOnLatestVersion, setRunOnLatestVersion] = useState(false);
 
   const [note, setNote] = useState<string | null>(taskInstance.note);
   const { isPending: isPendingPatchDagRun, mutate: mutatePatchTaskInstance } = 
usePatchTaskInstance({
@@ -66,6 +68,11 @@ const ClearTaskInstanceDialog = ({ onClose, open, 
taskInstance }: Props) => {
     taskId,
   });
 
+  // Get current DAG's bundle version to compare with task instance's DAG 
version bundle version
+  const { data: dagDetails } = useDagServiceGetDagDetails({
+    dagId,
+  });
+
   const { data } = useClearTaskInstancesDryRun({
     dagId,
     options: {
@@ -79,6 +86,7 @@ const ClearTaskInstanceDialog = ({ onClose, open, 
taskInstance }: Props) => {
       include_past: past,
       include_upstream: upstream,
       only_failed: onlyFailed,
+      run_on_latest_version: runOnLatestVersion,
       task_ids: [[taskId, mapIndex]],
     },
   });
@@ -88,6 +96,15 @@ const ClearTaskInstanceDialog = ({ onClose, open, 
taskInstance }: Props) => {
     total_entries: 0,
   };
 
+  // Check if bundle versions are different
+  const currentDagBundleVersion = dagDetails?.bundle_version;
+  const taskInstanceDagVersionBundleVersion = 
taskInstance.dag_version.bundle_version;
+  const bundleVersionsDiffer = currentDagBundleVersion !== 
taskInstanceDagVersionBundleVersion;
+  const shouldShowBundleVersionOption =
+    bundleVersionsDiffer &&
+    taskInstanceDagVersionBundleVersion !== null &&
+    taskInstanceDagVersionBundleVersion !== "";
+
   return (
     <Dialog.Root lazyMount onOpenChange={onClose} open={open} size="xl">
       <Dialog.Content backdrop>
@@ -140,7 +157,19 @@ const ClearTaskInstanceDialog = ({ onClose, open, 
taskInstance }: Props) => {
             />
           </Flex>
           <ActionAccordion affectedTasks={affectedTasks} note={note} 
setNote={setNote} />
-          <Flex justifyContent="end" mt={3}>
+          <Flex
+            {...(shouldShowBundleVersionOption ? { alignItems: "center" } : 
{})}
+            justifyContent={shouldShowBundleVersionOption ? "space-between" : 
"end"}
+            mt={3}
+          >
+            {shouldShowBundleVersionOption ? (
+              <Checkbox
+                checked={runOnLatestVersion}
+                onCheckedChange={(event) => 
setRunOnLatestVersion(Boolean(event.checked))}
+              >
+                
{translate("dags:runAndTaskActions.options.runOnLatestVersion")}
+              </Checkbox>
+            ) : undefined}
             <Button
               colorPalette="blue"
               disabled={affectedTasks.total_entries === 0}
@@ -156,6 +185,7 @@ const ClearTaskInstanceDialog = ({ onClose, open, 
taskInstance }: Props) => {
                     include_past: past,
                     include_upstream: upstream,
                     only_failed: onlyFailed,
+                    run_on_latest_version: runOnLatestVersion,
                     task_ids: [[taskId, mapIndex]],
                   },
                 });
diff --git a/airflow-core/src/airflow/ui/src/components/ui/index.ts 
b/airflow-core/src/airflow/ui/src/components/ui/index.ts
index 9339646d415..2146daedb7a 100644
--- a/airflow-core/src/airflow/ui/src/components/ui/index.ts
+++ b/airflow-core/src/airflow/ui/src/components/ui/index.ts
@@ -33,3 +33,4 @@ export * from "./Toaster";
 export * from "./Breadcrumb";
 export * from "./Clipboard";
 export * from "./Popover";
+export * from "./Checkbox";
diff --git 
a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_task_instances.py
 
b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_task_instances.py
index 16ee780f076..b2e40ef5d5f 100644
--- 
a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_task_instances.py
+++ 
b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_task_instances.py
@@ -2339,7 +2339,7 @@ class 
TestPostClearTaskInstances(TestTaskInstanceEndpoint):
 
         # dag (3rd argument) is a different session object. Manually asserting 
that the dag_id
         # is the same.
-        mock_clearti.assert_called_once_with([], mock.ANY, DagRunState.QUEUED)
+        mock_clearti.assert_called_once_with([], mock.ANY, DagRunState.QUEUED, 
run_on_latest_version=False)
 
     def test_clear_taskinstance_is_called_with_invalid_task_ids(self, 
test_client, session):
         """Test that dagrun is running when invalid task_ids are passed to 
clearTaskInstances API."""
diff --git 
a/airflow-core/tests/unit/api_fastapi/execution_api/versions/v2025_04_28/test_task_instances.py
 
b/airflow-core/tests/unit/api_fastapi/execution_api/versions/v2025_04_28/test_task_instances.py
index b5ef9a3ce5e..5757a3f6465 100644
--- 
a/airflow-core/tests/unit/api_fastapi/execution_api/versions/v2025_04_28/test_task_instances.py
+++ 
b/airflow-core/tests/unit/api_fastapi/execution_api/versions/v2025_04_28/test_task_instances.py
@@ -21,7 +21,8 @@ from unittest.mock import patch
 
 import pytest
 
-from airflow.api_fastapi.common.dagbag import create_dag_bag, dag_bag_from_app
+from airflow.api_fastapi.common.dagbag import dag_bag_from_app
+from airflow.jobs.scheduler_job_runner import SchedulerDagBag
 from airflow.utils import timezone
 from airflow.utils.state import State
 
@@ -106,9 +107,7 @@ class TestTIUpdateState:
             start_date=instant,
         )
 
-        dag = ti.task.dag
-        dagbag = create_dag_bag()
-        dagbag.dags = {dag.dag_id: dag}
+        dagbag = SchedulerDagBag()
         execution_app = get_execution_app(ver_client)
         execution_app.dependency_overrides[dag_bag_from_app] = lambda: dagbag
         session.commit()
diff --git a/airflow-core/tests/unit/cli/commands/test_backfill_command.py 
b/airflow-core/tests/unit/cli/commands/test_backfill_command.py
index ff23765fdd4..1c87d8c2ef8 100644
--- a/airflow-core/tests/unit/cli/commands/test_backfill_command.py
+++ b/airflow-core/tests/unit/cli/commands/test_backfill_command.py
@@ -101,6 +101,34 @@ class TestCliBackfill:
             dag_run_conf=None,
             reprocess_behavior=expected_repro,
             triggering_user_name="root",
+            run_on_latest_version=False,
+        )
+
+    @mock.patch("airflow.cli.commands.backfill_command._create_backfill")
+    def test_backfill_with_run_on_latest_version(self, mock_create):
+        args = [
+            "backfill",
+            "create",
+            "--dag-id",
+            "example_bash_operator",
+            "--from-date",
+            DEFAULT_DATE.isoformat(),
+            "--to-date",
+            DEFAULT_DATE.isoformat(),
+            "--run-on-latest-version",
+        ]
+        
airflow.cli.commands.backfill_command.create_backfill(self.parser.parse_args(args))
+
+        mock_create.assert_called_once_with(
+            dag_id="example_bash_operator",
+            from_date=DEFAULT_DATE,
+            to_date=DEFAULT_DATE,
+            max_active_runs=None,
+            reverse=False,
+            dag_run_conf=None,
+            reprocess_behavior=None,
+            run_on_latest_version=True,
+            triggering_user_name="root",
         )
 
     @mock.patch("airflow.cli.commands.backfill_command._do_dry_run")
diff --git a/airflow-core/tests/unit/models/test_backfill.py 
b/airflow-core/tests/unit/models/test_backfill.py
index e69ff85aa04..109d2145941 100644
--- a/airflow-core/tests/unit/models/test_backfill.py
+++ b/airflow-core/tests/unit/models/test_backfill.py
@@ -154,7 +154,8 @@ def test_create_backfill_simple(reverse, existing, 
dag_maker, session):
     assert all(x.conf == expected_run_conf for x in dag_runs)
 
 
-def test_create_backfill_clear_existing_bundle_version(dag_maker, session):
[email protected]("run_on_latest_version", [True, False])
+def test_create_backfill_clear_existing_bundle_version(dag_maker, session, 
run_on_latest_version):
     """
     Verify that when backfill clears an existing dag run, bundle version is 
cleared.
     """
@@ -193,11 +194,13 @@ def 
test_create_backfill_clear_existing_bundle_version(dag_maker, session):
         dag_run_conf=None,
         triggering_user_name="pytest",
         reprocess_behavior=ReprocessBehavior.FAILED,
+        run_on_latest_version=run_on_latest_version,
     )
     session.commit()
 
     # verify that the old dag run (not included in backfill) still has first 
bundle version
-    # but the latter 5, which are included in the backfill, have the latest 
bundle version
+    # but the latter 5, which are included in the backfill, have the latest 
bundle version if run_on_latest_version
+    # is True, otherwise they have the first bundle version
     dag_runs = sorted(
         session.scalars(
             select(DagRun).where(
@@ -206,7 +209,15 @@ def 
test_create_backfill_clear_existing_bundle_version(dag_maker, session):
         ),
         key=lambda x: x.logical_date,
     )
-    expected = [first_bundle_version] + 5 * [new_bundle_version]
+    if run_on_latest_version:
+        expected = [first_bundle_version] + 5 * [new_bundle_version]
+    else:
+        expected = (
+            [first_bundle_version]
+            + [new_bundle_version]
+            + 2 * [first_bundle_version]
+            + 2 * [new_bundle_version]
+        )
     assert [x.bundle_version for x in dag_runs] == expected
 
 
diff --git a/airflow-core/tests/unit/models/test_dag.py 
b/airflow-core/tests/unit/models/test_dag.py
index 9a99eba5974..dab3d4d9916 100644
--- a/airflow-core/tests/unit/models/test_dag.py
+++ b/airflow-core/tests/unit/models/test_dag.py
@@ -1480,8 +1480,7 @@ class TestDag:
 
     def test_dag_test_basic(self):
         dag = DAG(dag_id="test_local_testing_conn_file", schedule=None, 
start_date=DEFAULT_DATE)
-        dag.sync_to_db()
-        SerializedDagModel.write_dag(dag, bundle_name="testing")
+
         mock_object = mock.MagicMock()
 
         @task_decorator
@@ -1491,14 +1490,14 @@ class TestDag:
 
         with dag:
             check_task()
+        dag.sync_to_db()
+        SerializedDagModel.write_dag(dag, bundle_name="testing")
 
         dag.test()
         mock_object.assert_called_once()
 
     def test_dag_test_with_dependencies(self):
         dag = DAG(dag_id="test_local_testing_conn_file", schedule=None, 
start_date=DEFAULT_DATE)
-        dag.sync_to_db()
-        SerializedDagModel.write_dag(dag, bundle_name="testing")
         mock_object = mock.MagicMock()
 
         @task_decorator
@@ -1513,6 +1512,9 @@ class TestDag:
         with dag:
             check_task_2(check_task())
 
+        dag.sync_to_db()
+        SerializedDagModel.write_dag(dag, bundle_name="testing")
+
         dag.test()
         mock_object.assert_called_with("output of first task")
 
@@ -1538,8 +1540,6 @@ class TestDag:
 
         mock_task_object_1 = mock.MagicMock()
         mock_task_object_2 = mock.MagicMock()
-        dag.sync_to_db()
-        SerializedDagModel.write_dag(dag, bundle_name="testing")
 
         @task_decorator
         def check_task():
@@ -1553,7 +1553,8 @@ class TestDag:
 
         with dag:
             check_task_2(check_task())
-
+        dag.sync_to_db()
+        SerializedDagModel.write_dag(dag, bundle_name="testing")
         dr = dag.test()
 
         ti1 = dr.get_task_instance("check_task")
diff --git a/airflow-ctl/src/airflowctl/api/datamodels/generated.py 
b/airflow-ctl/src/airflowctl/api/datamodels/generated.py
index 0fe1d48ae40..0910c25a074 100644
--- a/airflow-ctl/src/airflowctl/api/datamodels/generated.py
+++ b/airflow-ctl/src/airflowctl/api/datamodels/generated.py
@@ -201,6 +201,13 @@ class ClearTaskInstancesBody(BaseModel):
     include_downstream: Annotated[bool | None, Field(title="Include 
Downstream")] = False
     include_future: Annotated[bool | None, Field(title="Include Future")] = 
False
     include_past: Annotated[bool | None, Field(title="Include Past")] = False
+    run_on_latest_version: Annotated[
+        bool | None,
+        Field(
+            description="(Experimental) Run on the latest bundle version of 
the DAG after clearing the task instances.",
+            title="Run On Latest Version",
+        ),
+    ] = False
 
 
 class Value(RootModel[list]):
@@ -308,6 +315,13 @@ class DAGRunClearBody(BaseModel):
     )
     dry_run: Annotated[bool | None, Field(title="Dry Run")] = True
     only_failed: Annotated[bool | None, Field(title="Only Failed")] = False
+    run_on_latest_version: Annotated[
+        bool | None,
+        Field(
+            description="(Experimental) Run on the latest bundle version of 
the DAG after clearing the DAG Run.",
+            title="Run On Latest Version",
+        ),
+    ] = False
 
 
 class DAGRunPatchStates(str, Enum):
diff --git a/providers/common/sql/tests/unit/common/sql/operators/test_sql.py 
b/providers/common/sql/tests/unit/common/sql/operators/test_sql.py
index 489e228dd80..23065cd3a71 100644
--- a/providers/common/sql/tests/unit/common/sql/operators/test_sql.py
+++ b/providers/common/sql/tests/unit/common/sql/operators/test_sql.py
@@ -1140,7 +1140,7 @@ class TestSqlBranch:
 
     @pytest.fixture
     def branch_op(self):
-        return BranchSQLOperator(
+        branch_op = BranchSQLOperator(
             task_id="make_choice",
             conn_id="mysql_default",
             sql="SELECT 1",
@@ -1148,6 +1148,10 @@ class TestSqlBranch:
             follow_task_ids_if_false=["branch_2"],
             dag=self.dag,
         )
+        if AIRFLOW_V_3_0_PLUS:
+            self.dag.sync_to_db()
+            SerializedDagModel.write_dag(self.dag, bundle_name="testing")
+        return branch_op
 
     def test_unsupported_conn_type(self):
         """Check if BranchSQLOperator throws an exception for unsupported 
connection type"""
@@ -1387,6 +1391,8 @@ class TestSqlBranch:
         self.dag.clear()
 
         if AIRFLOW_V_3_0_PLUS:
+            self.dag.sync_to_db()
+            SerializedDagModel.write_dag(self.dag, bundle_name="testing")
             dagrun_kwargs = {
                 "logical_date": DEFAULT_DATE,
                 "run_after": DEFAULT_DATE,
diff --git a/providers/standard/tests/unit/standard/decorators/test_bash.py 
b/providers/standard/tests/unit/standard/decorators/test_bash.py
index f4303b345eb..e53e8e75f6b 100644
--- a/providers/standard/tests/unit/standard/decorators/test_bash.py
+++ b/providers/standard/tests/unit/standard/decorators/test_bash.py
@@ -342,7 +342,7 @@ class TestBashDecorator:
         """Verify task failure for non-existent, user-defined working 
directory."""
         cwd_path = tmp_path / "test_cwd"
 
-        with self.dag:
+        with self.dag_maker():
 
             @task.bash(cwd=os.fspath(cwd_path))
             def bash():
@@ -363,7 +363,7 @@ class TestBashDecorator:
         cwd_file = tmp_path / "testfile.var.env"
         cwd_file.touch()
 
-        with self.dag:
+        with self.dag_maker():
 
             @task.bash(cwd=os.fspath(cwd_file))
             def bash():
@@ -381,7 +381,7 @@ class TestBashDecorator:
 
     def test_command_not_found(self):
         """Fail task if executed command is not found on path."""
-        with self.dag:
+        with self.dag_maker():
 
             @task.bash
             def bash():
@@ -481,7 +481,7 @@ class TestBashDecorator:
 
     def test_rtif_updates_upon_failure(self):
         """Veriy RenderedTaskInstanceField data should contain the rendered 
command even if the task fails."""
-        with self.dag:
+        with self.dag_maker():
 
             @task.bash
             def bash():
diff --git a/providers/standard/tests/unit/standard/operators/test_python.py 
b/providers/standard/tests/unit/standard/operators/test_python.py
index 7a3ad2167a9..95c503aed70 100644
--- a/providers/standard/tests/unit/standard/operators/test_python.py
+++ b/providers/standard/tests/unit/standard/operators/test_python.py
@@ -551,11 +551,11 @@ class TestBranchOperator(BasePythonTest):
             ("join", [State.SUCCESS, State.SKIPPED, State.SUCCESS]),
         ],
     )
-    def test_empty_branch(self, choice, expected_states):
+    def test_empty_branch(self, choice, expected_states, session):
         """
         Tests that BranchPythonOperator handles empty branches properly.
         """
-        with self.dag_non_serialized:
+        with self.dag_maker(self.dag_id, 
template_searchpath=TEMPLATE_SEARCHPATH, serialized=True):
 
             def f():
                 return choice
@@ -567,13 +567,13 @@ class TestBranchOperator(BasePythonTest):
             branch >> [task1, join]
             task1 >> join
 
-        dr = self.create_dag_run()
+        dr = self.dag_maker.create_dagrun()
         task_ids = [self.task_id, "task1", "join"]
         tis = {ti.task_id: ti for ti in dr.task_instances}
 
         for task_id in task_ids:  # Mimic the specific order the scheduling 
would run the tests.
             task_instance = tis[task_id]
-            
task_instance.refresh_from_task(self.dag_non_serialized.get_task(task_id))
+            
task_instance.refresh_from_task(self.dag_maker.dag.get_task(task_id))
             if AIRFLOW_V_3_0_1:
                 from airflow.exceptions import DownstreamTasksSkipped
 
@@ -758,10 +758,11 @@ class TestShortCircuitOperator(BasePythonTest):
         After a downstream task is skipped by ShortCircuitOperator, clearing 
the skipped task
         should not cause it to be executed.
         """
-        with self.dag_non_serialized:
+        with self.dag_maker(self.dag_id, 
template_searchpath=TEMPLATE_SEARCHPATH, serialized=True):
             short_circuit = ShortCircuitOperator(task_id=self.task_id, 
python_callable=lambda: False)
             short_circuit >> self.op1 >> self.op2
-        dr = self.create_dag_run()
+
+        dr = self.dag_maker.create_dagrun()
 
         if AIRFLOW_V_3_0_1:
             from airflow.exceptions import DownstreamTasksSkipped
diff --git a/task-sdk/src/airflow/sdk/execution_time/supervisor.py 
b/task-sdk/src/airflow/sdk/execution_time/supervisor.py
index e36b3667e2e..2e8d46e5e74 100644
--- a/task-sdk/src/airflow/sdk/execution_time/supervisor.py
+++ b/task-sdk/src/airflow/sdk/execution_time/supervisor.py
@@ -1402,20 +1402,17 @@ class InProcessTestSupervisor(ActivitySubprocess):
 
     @staticmethod
     def _api_client(dag=None):
-        from airflow.models.dagbag import DagBag
         from airflow.sdk.api.client import Client
 
         api = in_process_api_server()
         if dag is not None:
             from airflow.api_fastapi.common.dagbag import dag_bag_from_app
-            from airflow.serialization.serialized_objects import SerializedDAG
+            from airflow.jobs.scheduler_job_runner import SchedulerDagBag
 
-            # This is needed since the Execution API server uses the DagBag in 
its "state".
+            # This is needed since the Execution API server uses the 
SchedulerDagBag in its "state".
             # This `app.state.dag_bag` is used to get some DAG properties like 
`fail_fast`.
-            dag_bag = DagBag(include_examples=False, collect_dags=False, 
load_op_links=False)
+            dag_bag = SchedulerDagBag()
 
-            # Mimic the behavior of the DagBag in the API server by converting 
the DAG to a SerializedDAG
-            dag_bag.dags[dag.dag_id] = 
SerializedDAG.from_dict(SerializedDAG.to_dict(dag))
             api.app.dependency_overrides[dag_bag_from_app] = lambda: dag_bag
 
         client = Client(base_url=None, token="", dry_run=True, 
transport=api.transport)

Reply via email to