Skip to content

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

Closed

Description

@shivaam

Under which category would you file this issue?

Airflow Core (API, UI)

Apache Airflow version

main (57a4751c967)

What happened and how to reproduce it?

PATCH .../taskInstances/{task_id}/{map_index}/hitlDetails records the response whenever the
request has none yet. It resumes the task only if the task is still awaiting_input or deferred.

When response_timeout passes with no defaults, the scheduler fails the task without writing a
response. A response submitted afterwards is therefore stored and answered with 200, and the UI shows
"response successful", but nothing resumes. The task stays failed, and the history shows a decision
that never took effect.

Reproduce:

  1. ApprovalOperator(subject="...", response_timeout=timedelta(seconds=20)), with no defaults
    and retries=0.
  2. Open its Required Actions page, then wait for the task to fail on timeout without reloading.
  3. Click Approve in the page that is still open. The UI reports success and the history records the
    approval. The task and its downstream tasks remain failed or upstream_failed.

A freshly opened page disables the buttons. Only a page loaded before the timeout lets this through,
and the API accepts it from any client.

What you think should happen instead?

A response to a request whose task is no longer waiting for it is refused, for example with 409, with
a message that the request expired or the task stopped waiting. It should not be recorded as a
decision.

Anything else?


Drafted-by: Claude Code (Opus 5.5) (no human review before posting)

Activity

  1. estella00911 commented on Oct 25, 2026

    @estella00911

    I am looking into this issue.

  2. amoghrajesh commented on Oct 26, 2026

    @amoghrajesh
    Contributor

    @estella00911 if you're looking into this (or anyone later) - the write and the resume are not gated by the same check. The resume is gated on the task instance state whereas the write is not. The fix would be to decide once, before anything is stored, and return 409 when the task is not waiting anymore.

  3. estella00911 commented on Oct 26, 2026

    @estella00911

    Thanks @amoghrajesh for the clarification!

    I tried a similar state check that only allows AWAITING_INPUT and DEFERRED, but I noticed two possible RUNNING scenarios:

    • Early response (200): RUNNING → HITL request created → User responds before parking → SCHEDULED
    • Timeout resume (409): AWAITING_INPUT → Timeout → SCHEDULED → RUNNING (handling timeout event) → FAILED

    I found that TIAwaitingInputStatePayload seems to handle early responses.

    Should we also consider the RUNNING state when deciding whether to return 409?

    I reproduced the original issue, but haven't reproduced these RUNNING scenarios myself yet.

  4. added
    priority:mediumBug that should be fixed before next release but would not block a release
    on Oct 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    affected_version:3.4.0rcCan be used for bugs identified during 3.4.0 testingneeds-triagelabel for new issues that we didn't triage yetpriority:mediumBug that should be fixed before next release but would not block a release

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions