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"),
[