Skip to content

Refuse Human-in-the-loop responses once the task stopped waiting - #75645

Merged
vatsrahul1001 merged 3 commits into
apache:mainfrom
astronomer:hitl-reject-response-after-stopped-waiting
Oct 26, 2026
Merged

vatsrahul1001 merged 3 commits into
apache:mainfrom
astronomer:hitl-reject-response-after-stopped-waiting

Conversation

@Lee-W

@Lee-W Lee-W commented Oct 26, 2026 •

Copy link
Copy Markdown
Member

Why

Once a Human-in-the-loop task stops waiting, for example after its response_timeout
expires and it fails because no defaults were set, the hitlDetails PATCH used to keep
accepting answers. The answer was stored and the API returned 200, but nothing resumed the task. A user answering from a page that was still open saw a success message for a decision that never took effect.

The UI error toast made the refusal hard to understand as well: it only showed the HTTP
status text ("Conflict"), which says nothing about why the answer was not accepted.

Closes: #75531

What

  • The hitlDetails PATCH returns 409 and records nothing when the task instance is not
    waiting (awaiting_input or deferred). The check runs under the existing task instance lock, after authorization and option validation, so it cannot interleave with a concurrent clear.
  • The error toast shows the detail the backend returns, and falls back to the previous
    message when there is none.

Behaviour worth calling out for review:

  • The gate looks at task state only, not at whether the timeout has passed. A request that arrives after the timeout but before the scheduler has processed it is still accepted, because the task is still waiting and the response will resume it. Comparing against the timeout in the API would duplicate the scheduler's decision and could refuse an answer that the scheduler would have honoured. A test pins this case.
  • The shared createErrorToaster is not changed. It is used in roughly 20 places across the UI, and changing its behaviour would affect all of them. The detail handling lives in useUpdateHITLDetail, and 403 is still left to the MutationCache handler as before.
image
Was generative AI tooling used to co-author this PR?
  • Yes (please specify the tool below)

Generated-by: [Claude] following the guidelines


  • Read the Pull Request Guidelines for more information. Note: commit author/co-author name and email in commits become permanently public when merged.
  • For fundamental code changes, an Airflow Improvement Proposal (AIP) is needed.
  • When adding dependency, check compliance with the ASF 3rd Party License Policy.
  • For significant user-facing changes create newsfragment: {pr_number}.significant.rst, in airflow-core/newsfragments. You can add this file in a follow-up commit after the PR is created so you know the PR number.

@boring-cyborg boring-cyborg Bot added the area:API Airflow's REST/HTTP API label Oct 26, 2026
@Lee-W
Lee-W force-pushed the hitl-reject-response-after-stopped-waiting branch 2 times, most recently from 9d56237 to 88b398f Compare October 26, 2026 10:30
@Lee-W
Lee-W marked this pull request as ready for review October 26, 2026 10:36
@Lee-W
Lee-W force-pushed the hitl-reject-response-after-stopped-waiting branch from 88b398f to c58e0c9 Compare October 26, 2026 10:38
@Lee-W Lee-W added the backport-to-v3-4-test Backport to v3-4-test label Oct 26, 2026
Lee-W added 3 commits October 26, 2026 22:45
The hitlDetails PATCH recorded a response whenever none existed yet and
only resumed the task if it was still awaiting input or deferred. After
a response_timeout without defaults, an answer was stored and returned
200 although nothing resumed, so the UI reported success for a decision
that never took effect.

Return 409 before writing when the task instance is in any other state.
The check runs under the existing task instance lock, after the
authorization and option validation, and looks at the state only: a
request whose timeout has passed but that the scheduler has not yet
processed is still accepted.
The error toast showed only the HTTP status text ("Conflict") when a
response was refused. Show the detail the backend returns, such as that
the task is no longer waiting for a response, and fall back to the
previous message when there is none.
@Lee-W
Lee-W force-pushed the hitl-reject-response-after-stopped-waiting branch from c58e0c9 to b53a232 Compare October 26, 2026 13:45
@Lee-W Lee-W added this to the Airflow 3.4.0 milestone Oct 26, 2026
@vatsrahul1001
vatsrahul1001 merged commit d1491b8 into apache:main Oct 26, 2026
89 checks passed
@vatsrahul1001
vatsrahul1001 deleted the hitl-reject-response-after-stopped-waiting branch October 26, 2026 17:37
@github-actions

Copy link
Copy Markdown
Contributor

Backport failed to create: v3-4-test. View the failure log Run details

Note: As of Merging PRs targeted for Airflow 3.X
the committer who merges the PR is responsible for backporting the PRs that are bug fixes (generally speaking) to the maintenance branches.

In matter of doubt please ask in #release-management Slack channel.

Status Branch Result
❌ v3-4-test Commit Link

You can attempt to backport this manually by running:

cherry_picker d1491b8 v3-4-test

This should apply the commit to the v3-4-test branch and leave the commit in conflict state marking
the files that need manual conflict resolution.

After you have resolved the conflicts, you can continue the backport process by running:

cherry_picker --continue

If you don't have cherry-picker installed, see the installation guide.

vatsrahul1001 pushed a commit that referenced this pull request Oct 27, 2026
) (#75706)

* Refuse Human-in-the-loop responses once the task stopped waiting

The hitlDetails PATCH recorded a response whenever none existed yet and
only resumed the task if it was still awaiting input or deferred. After
a response_timeout without defaults, an answer was stored and returned
200 although nothing resumed, so the UI reported success for a decision
that never took effect.

Return 409 before writing when the task instance is in any other state.
The check runs under the existing task instance lock, after the
authorization and option validation, and looks at the state only: a
request whose timeout has passed but that the scheduler has not yet
processed is still accepted.

* Show why the backend refused a Human-in-the-loop response

The error toast showed only the HTTP status text ("Conflict") when a
response was refused. Show the detail the backend returns, such as that
the task is no longer waiting for a response, and fall back to the
previous message when there is none.

* Cover the refusal toast for validation, mapping, missing and forbidden details

(cherry picked from commit d1491b8)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:API Airflow's REST/HTTP API backport-to-v3-4-test Backport to v3-4-test

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Human-in-the-loop response is accepted after the task has stopped waiting

2 participants