Repository navigation
Fix AirflowFailException in on_retry_callback not preventing retries #64198
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| ``AirflowFailException`` raised inside ``on_retry_callback`` now correctly prevents task retries and marks the task as failed. |
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -1355,7 +1355,7 @@ def _on_term(signum, frame): | |||
| # Same metric with tagging | ||||
| Stats.incr("ti.finish", tags={**stats_tags, "state": state.value}) | ||||
|
|
||||
| if msg: | ||||
| if msg and state != TaskInstanceState.UP_FOR_RETRY: | ||||
| SUPERVISOR_COMMS.send(msg=msg) | ||||
|
|
||||
| # Return the message to make unit tests easier too | ||||
|
|
@@ -1538,10 +1538,14 @@ def _run_task_state_change_callbacks( | |||
| context: Context, | ||||
| log: Logger, | ||||
| ) -> None: | ||||
| from airflow.sdk.exceptions import AirflowFailException | ||||
|
|
||||
|
Comment on lines
+1541
to
+1542
|
||||
| from airflow.sdk.exceptions import AirflowFailException |
Copilot
AI
Apr 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar to _run_task_state_change_callbacks, this local import of AirflowFailException is unnecessary (it’s already available at module scope in this file) and adds repeated imports in a hot path. Prefer using the existing module-level import unless there’s a specific circular-import / isolation reason.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't this handled by the code deleted below either way?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The msg was originally returned for testing, I would suggest either removing the comment, or understand if we should use it in the finalize or non test code (I still think it is better to handle in the run as much as possible)
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -4360,6 +4360,53 @@ class CustomOperator(BaseOperator): | |||||
| expected_exception_logs.insert(index, calls) | ||||||
| assert log.exception.mock_calls == expected_exception_logs | ||||||
|
|
||||||
| def test_airflow_fail_exception_in_on_retry_callback_marks_task_as_failed( | ||||||
| self, create_runtime_ti, mock_supervisor_comms | ||||||
| ): | ||||||
| """AirflowFailException raised in on_retry_callback must prevent retries and mark task as FAILED.""" | ||||||
| from airflow.sdk.exceptions import AirflowFailException | ||||||
|
|
||||||
|
Comment on lines
+4367
to
+4368
|
||||||
| from airflow.sdk.exceptions import AirflowFailException |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use mocks and side effects instead of manually counting call counts
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above
Copilot
AI
Apr 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test introduces an unspecced mock.MagicMock() for log. The repo’s testing guidelines prefer mocks with spec/autospec to avoid silently accepting invalid attributes (e.g. mock.MagicMock(spec=FilteringBoundLogger) or whichever logger interface run() expects).
| log = mock.MagicMock() | |
| log = mock.MagicMock(spec=type(runtime_ti.log)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe it is a good idea to handle the case here?
Rather than rely on a different method called elsewhere, as to me, it seems like this is an undocumented assumption that it will be handled later, at the very least I would add a comment clarifying it, preferably I would explicitly check if the state is up for retry AND there is message and handle it right there (can be extracted to a method but should be obvious where the handling occurs)