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

vatsrahul1001 pushed a commit to branch v3-3-test
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/v3-3-test by this push:
     new 84cd209ffd2 [v3-3-test] Return 404 instead of 500 when ti_run cannot 
find the DagRun (#72890) (#72900)
84cd209ffd2 is described below

commit 84cd209ffd28235cb2730c28d175292ce51e0b2c
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Fri Sep 11 14:25:06 2026 +0530

    [v3-3-test] Return 404 instead of 500 when ti_run cannot find the DagRun 
(#72890) (#72900)
    
    * [v3-3-test] Return 404 instead of 500 when ti_run cannot find the DagRun 
(#72890)
    
    * Return 404 instead of 500 when ti_run cannot find the DagRun
    
    A missing DagRun in the Execution API ti_run route raised a bare
    ValueError, which escaped the route's DataError/SQLAlchemyError guards and
    surfaced through the app-level catch-all as an opaque 500. A missing
    resource should be reported to the caller as a 404, consistent with the
    other not-found paths in this route.
    
    * Update 
airflow-core/src/airflow/api_fastapi/execution_api/routes/task_instances.py
    
    Co-authored-by: Henry Chen <[email protected]>
    
    * Align test with the updated missing-DagRun error message
    
    * Keep the ti_run 404 test's scalars mock out of fixture setup
    
    Patching Session.scalars as a decorator also mocked it while
    create_task_instance built the fixture, so the DAG bulk-write only worked
    because the tables were cleared first. Scope the patch to the request
    itself, matching the sibling database-error test.
    
    ---------
    (cherry picked from commit a9f6c898f01dc1e5e1dc18172f5bc65097957a71)
    
    Co-authored-by: Pierre Jeambrun <[email protected]>
    Co-authored-by: Henry Chen <[email protected]>
    
    * Define RUN_PAYLOAD on TestTIRunState so the ti_run 404 test can build its 
request
    
    ---------
    
    Co-authored-by: Pierre Jeambrun <[email protected]>
    Co-authored-by: Henry Chen <[email protected]>
    Co-authored-by: Rahul Vats <[email protected]>
    Co-authored-by: Rahul Vats <[email protected]>
---
 .../execution_api/routes/task_instances.py         |  8 +++++-
 .../versions/head/test_task_instances.py           | 32 ++++++++++++++++++++++
 2 files changed, 39 insertions(+), 1 deletion(-)

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 83d8dc7c51e..f8b8d7e7de1 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
@@ -265,7 +265,13 @@ def ti_run(
 
         if not dr:
             log.error("DagRun not found", dag_id=ti.dag_id, run_id=ti.run_id)
-            raise ValueError(f"DagRun with dag_id={ti.dag_id} and 
run_id={ti.run_id} not found.")
+            raise HTTPException(
+                status_code=status.HTTP_404_NOT_FOUND,
+                detail={
+                    "reason": "not_found",
+                    "message": f"DagRun with dag_id={ti.dag_id} and 
run_id={ti.run_id} not found",
+                },
+            )
 
         # Send the keys to the SDK so that the client requests to clear those 
XComs from the server.
         # The reason we cannot do this here in the server is because we need 
to issue a purge on custom XCom backends
diff --git 
a/airflow-core/tests/unit/api_fastapi/execution_api/versions/head/test_task_instances.py
 
b/airflow-core/tests/unit/api_fastapi/execution_api/versions/head/test_task_instances.py
index 65bbe0ff77c..7d4d5cc7433 100644
--- 
a/airflow-core/tests/unit/api_fastapi/execution_api/versions/head/test_task_instances.py
+++ 
b/airflow-core/tests/unit/api_fastapi/execution_api/versions/head/test_task_instances.py
@@ -163,6 +163,14 @@ def test_id_matches_sub_claim(client, session, 
create_task_instance):
 
 
 class TestTIRunState:
+    RUN_PAYLOAD = {
+        "state": "running",
+        "hostname": "random-hostname",
+        "unixname": "random-unixname",
+        "pid": 100,
+        "start_date": "2024-10-31T12:00:00Z",
+    }
+
     def setup_method(self):
         clear_db_logs()
         clear_db_runs()
@@ -207,6 +215,30 @@ class TestTIRunState:
         events = response.json()["dag_run"]["consumed_asset_events"]
         assert [e["partition_key"] for e in events] == ["2024-01-15"]
 
+    def test_ti_run_missing_dagrun_returns_404(self, client, session, 
create_task_instance):
+        """A missing DagRun must surface as a clean 404, not an internal 
500."""
+        ti = create_task_instance(
+            task_id="test_ti_run_missing_dagrun",
+            state=State.QUEUED,
+            session=session,
+        )
+        session.commit()
+
+        # Patch only around the request so fixture setup above is untouched; 
force the DagRun
+        # lookup (the only scalars() call before the guard) to return None.
+        with mock.patch("sqlalchemy.orm.Session.scalars", autospec=True) as 
mock_scalars:
+            
mock_scalars.return_value.unique.return_value.one_or_none.return_value = None
+            response = client.patch(
+                f"/execution/task-instances/{ti.id}/run",
+                json=self.RUN_PAYLOAD,
+            )
+
+        assert response.status_code == 404
+        assert response.json()["detail"] == {
+            "reason": "not_found",
+            "message": f"DagRun with dag_id={ti.dag_id} and run_id={ti.run_id} 
not found",
+        }
+
     @pytest.mark.parametrize(
         ("max_tries", "should_retry"),
         [

Reply via email to