This is an automated email from the ASF dual-hosted git repository.
weilee 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 5619bf196e7 fix(hitl): Handle task instance clearing cases when
getting and updating an existing HITLDetail (#53863)
5619bf196e7 is described below
commit 5619bf196e7b2103b9a410cafaa3395756fac9f8
Author: Wei Lee <[email protected]>
AuthorDate: Wed Jul 30 15:29:55 2025 +0800
fix(hitl): Handle task instance clearing cases when getting and updating an
existing HITLDetail (#53863)
---
.../api_fastapi/execution_api/routes/hitl.py | 16 +++++++++++
.../execution_api/versions/head/test_hitl.py | 31 ++++++++++++++++++++++
2 files changed, 47 insertions(+)
diff --git a/airflow-core/src/airflow/api_fastapi/execution_api/routes/hitl.py
b/airflow-core/src/airflow/api_fastapi/execution_api/routes/hitl.py
index 36d50b19fa7..9a34392f3ad 100644
--- a/airflow-core/src/airflow/api_fastapi/execution_api/routes/hitl.py
+++ b/airflow-core/src/airflow/api_fastapi/execution_api/routes/hitl.py
@@ -85,6 +85,20 @@ def upsert_hitl_detail(
return HITLDetailRequest.model_validate(hitl_detail_model)
+def _check_hitl_detail_exists(hitl_detail_model: HITLDetail) -> None:
+ if not hitl_detail_model:
+ raise HTTPException(
+ status.HTTP_404_NOT_FOUND,
+ detail={
+ "reason": "not_found",
+ "message": (
+ "HITLDetail not found. "
+ "This happens most likely due to clearing task instance
before receiving response."
+ ),
+ },
+ )
+
+
@router.patch("/{task_instance_id}")
def update_hitl_detail(
task_instance_id: UUID,
@@ -94,6 +108,7 @@ def update_hitl_detail(
"""Update the response part of a Human-in-the-loop detail for a specific
Task Instance."""
ti_id_str = str(task_instance_id)
hitl_detail_model =
session.execute(select(HITLDetail).where(HITLDetail.ti_id ==
ti_id_str)).scalar()
+ _check_hitl_detail_exists(hitl_detail_model)
if hitl_detail_model.response_received:
raise HTTPException(
status.HTTP_409_CONFLICT,
@@ -122,4 +137,5 @@ def get_hitl_detail(
hitl_detail_model = session.execute(
select(HITLDetail).where(HITLDetail.ti_id == ti_id_str),
).scalar()
+ _check_hitl_detail_exists(hitl_detail_model)
return HITLDetailResponse.from_hitl_detail_orm(hitl_detail_model)
diff --git
a/airflow-core/tests/unit/api_fastapi/execution_api/versions/head/test_hitl.py
b/airflow-core/tests/unit/api_fastapi/execution_api/versions/head/test_hitl.py
index 90c6d6350db..264b0dba319 100644
---
a/airflow-core/tests/unit/api_fastapi/execution_api/versions/head/test_hitl.py
+++
b/airflow-core/tests/unit/api_fastapi/execution_api/versions/head/test_hitl.py
@@ -28,6 +28,7 @@ from datetime import datetime
from typing import TYPE_CHECKING, Any
import time_machine
+from uuid6 import uuid7
from airflow._shared.timezones.timezone import convert_to_utc
from airflow.models.hitl import HITLDetail
@@ -155,8 +156,38 @@ def test_update_hitl_detail(client: Client, sample_ti:
TaskInstance) -> None:
}
+def test_update_hitl_detail_without_ti(client: Client) -> None:
+ ti_id = str(uuid7())
+ response = client.patch(
+ f"/execution/hitl-details/{ti_id}",
+ json={
+ "ti_id": ti_id,
+ "chosen_options": ["Reject"],
+ "params_input": {"input_1": 2},
+ },
+ )
+ assert response.status_code == 404
+ assert response.json() == {
+ "detail": {
+ "message": "HITLDetail not found. This happens most likely due to
clearing task instance before receiving response.",
+ "reason": "not_found",
+ },
+ }
+
+
@pytest.mark.usefixtures("sample_hitl_detail")
def test_get_hitl_detail(client: Client, sample_ti: TaskInstance) -> None:
response = client.get(f"/execution/hitlDetails/{sample_ti.id}")
assert response.status_code == 200
assert response.json() == expected_empty_hitl_detail_response_part
+
+
+def test_get_hitl_detail_without_ti(client: Client) -> None:
+ response = client.get(f"/execution/hitl-details/{uuid7()}")
+ assert response.status_code == 404
+ assert response.json() == {
+ "detail": {
+ "message": "HITLDetail not found. This happens most likely due to
clearing task instance before receiving response.",
+ "reason": "not_found",
+ },
+ }