Skip to content

Make the default task failure email say what failed - #73221

Open
rjgoyln wants to merge 13 commits into
apache:mainfrom
rjgoyln:fix/runtime-ti-readable-str
Open

rjgoyln wants to merge 13 commits into
apache:mainfrom
rjgoyln:fix/runtime-ti-readable-str

Conversation

@rjgoyln

@rjgoyln rjgoyln commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

Summary

The default task failure email does not say what failed. Its subject renders {{ ti }}, which on Airflow 2 gave the TaskInstance repr and now dumps every field of a Pydantic model:

Airflow alert: id=UUID('019c3eb1-…') task_id='fail' dag_id='test-email-on-failure' run_id='manual__2026-02-08T19:18:47.078356+00:00' try_number=1 dag_version_id=UUID('019c3ea3-…') map_index=-1 context_carrier={} task=<Task(_PythonDecoratedOperator): fail> bundle_instance=GitHubAppDagBundle(name=github-app) … state=<TaskInstanceState.FAILED: 'failed'>

Neither body names the Dag or the task; the notifier's bundled template renders {{ ti.state }}, an enum whose str() is its Python name; and both offer a "Mark success" link that goes to the log, since Airflow 3 aliases mark_success_url to log_url.

  • both paths use the subject [Airflow] <dag_id>.<task_id> <state> - Run <run_id>
  • both bodies name the Dag, task, run, state and timestamps, and render the state's value
  • the "Mark success" link is gone unless the URL actually differs from the log URL
  • the bundled template's heading follows the state, so it still reads on the success and retry callbacks that share it
  • RuntimeTaskInstance.__str__ mirrors the scheduler-side TaskInstance.__repr__

The default subject no longer interpolates {{ ti }}, but subject_template files carried over from Airflow 2 do. It is __str__ rather than __repr__ so those read cleanly while a debugger still shows every field.

Emails sent from the Dag processor are for tasks that never reached their own callbacks, and that request carries no state, so they take it from the email type instead.

The template work is adapted from #67931, stale since July, where the heading was hardcoded to a failure. #73271 is folded in here because it rewrites the same template blocks.

Behavior change

Both subject formats change; providers/smtp/docs/changelog.rst carries the note for anyone whose mail filters match on the notifier's.

closes: #61667


Was generative AI tooling used to co-author this PR?
  • Yes — Claude Code (Opus 5)

Generated-by: Claude Code (Opus 5) following the guidelines

@rjgoyln rjgoyln changed the title Fix task failure emails dumping raw task instance fields Fix default task failure emails dumping Python internals Sep 17, 2026
@rjgoyln rjgoyln changed the title Fix default task failure emails dumping Python internals Make the default task failure email say what failed Sep 17, 2026
@rjgoyln
rjgoyln force-pushed the fix/runtime-ti-readable-str branch 2 times, most recently from 3d9ab8b to bee3249 Compare September 17, 2026 13:15
@rjgoyln
rjgoyln marked this pull request as ready for review September 17, 2026 13:31
Comment thread airflow-core/newsfragments/73221.bugfix.rst
Comment thread airflow-core/src/airflow/dag_processing/processor.py Outdated
Comment thread providers/smtp/tests/unit/smtp/notifications/test_smtp.py Outdated
Comment thread providers/smtp/tests/unit/smtp/notifications/test_smtp.py Outdated
Comment thread task-sdk/src/airflow/sdk/execution_time/task_runner.py Outdated
Comment thread task-sdk/src/airflow/sdk/execution_time/task_runner.py Outdated
Comment thread task-sdk/tests/task_sdk/execution_time/test_task_runner.py Outdated
Comment thread task-sdk/tests/task_sdk/execution_time/test_task_runner.py Outdated
@rjgoyln

rjgoyln commented Sep 27, 2026

Copy link
Copy Markdown
Contributor Author

Thanks, all addressed.

I’ve updated the state handling, removed the [Airflow] prefix, added autospec=True to the new mocks, and simplified the template assertions.

For the render test, I also switched to fixed create_runtime_ti values and literal expected output, so the test is independent of the implementation details.

@rjgoyln
rjgoyln force-pushed the fix/runtime-ti-readable-str branch 2 times, most recently from d6d4c05 to 8ad2d18 Compare September 27, 2026 11:14
rjgoyln added 13 commits October 8, 2026 18:58
The default email subject renders {{ ti }}, which on Airflow 2 produced
the TaskInstance repr. On Airflow 3 the worker-side task instance is a
Pydantic model with no textual form of its own, so every field — UUIDs,
the task object, the bundle instance — lands in the subject line, and
operators can no longer tell at a glance which task failed.
Adapted from apache#67931 by Wilmer Dooley, whose PR went stale. The bundled body
never named the Dag or the task, and the state field rendered the Python enum
repr rather than the state itself. The heading is derived from the state so
the template still reads correctly on the success and retry callbacks that
share it.
Review follow-up. The parametrized test took over the tail of the defaults
test, the state rationale was repeated in both templates, and the changed
subject needs a note for anyone whose mail filters match on it.
The legacy email path never said which Dag or task had failed, and neither
template carried the timestamps operators need to line the failure up against
the rest of a run.
The Dag-processor callback path builds its task instance from the callback
request alone, so it has no dates at all, and the Dag's Jinja environment is
StrictUndefined -- a bare truth test on the attribute aborts the whole email.
The scheduler-side column is not nullable, but the Task SDK model declares
map_index as optional and the surrounding code already guards for None, so
mirroring the scheduler check alone would print "map_index=None".
The legacy path still put the task instance repr in the subject, UUID and
all, which is what the issue reported; the two paths also disagreed on the
shape of the subject and on whether the body named the state.
Airflow 3 has no mark-success endpoint, so mark_success_url returns log_url
and the email offered the same destination twice under a label that promises
something it cannot do. Folded in from apache#73271, which this supersedes.
Review follow-up: the new variable is usable from custom subject and content
templates, so it belongs in the email configuration guide and the release note
rather than appearing unexplained in an example.
Those emails are for tasks that never reached their own callbacks, and the
callback request carries no state, so every one of them said the state was
unknown -- while the request's email type is exactly the reason it fired.
Drop the subject prefix, report a missing state as the UI does, and set the
Dag-processor state where the email type is already branched on. The templates
are no longer compared verbatim in tests; the rendered output is.
Rebase adaptation: the Python 3.10 drop moved ruff's target version, and it
now flags timezone.utc in favour of the alias.
@rjgoyln
rjgoyln force-pushed the fix/runtime-ti-readable-str branch from b52535a to 9635de5 Compare October 8, 2026 11:22

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improve default task failure email template for readability and operational clarity

2 participants