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

pierrejeambrun 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 a9f6c898f01 Return 404 instead of 500 when ti_run cannot find the 
DagRun (#72890)
a9f6c898f01 is described below

commit a9f6c898f01dc1e5e1dc18172f5bc65097957a71
Author: Pierre Jeambrun <[email protected]>
AuthorDate: Thu Sep 10 19:16:48 2026 +0200

    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.
    
    ---------
    
    Co-authored-by: Henry Chen <[email protected]>
---
 .../execution_api/routes/task_instances.py         |  8 +++++++-
 .../versions/head/test_task_instances.py           | 24 ++++++++++++++++++++++
 2 files changed, 31 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 bc00ccf07c9..6dde3b7a7ba 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
@@ -272,7 +272,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 44a1fe5e088..91c2ed75e09 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
@@ -216,6 +216,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