Skip to content

UI: Show the API's reason in error toasts instead of the status text - #76341

Draft
shivaam wants to merge 2 commits into
apache:mainfrom
shivaam:fix-ui-error-toast-detail
Draft

shivaam wants to merge 2 commits into
apache:mainfrom
shivaam:fix-ui-error-toast-detail

Conversation

@shivaam

@shivaam shivaam commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

When the API refuses an action, the error toast showed only the HTTP status text. createErrorToaster used error.message, which for an ApiError is the status text, so deleting a running Dag run toasted "Error deleting Dag Run / Conflict" while the response said why:

Toast
Before Error deleting Dag Run — Conflict
After Error deleting Dag Run — The DagRun with dag_id: `example` and run_id: `manual__…` cannot be deleted in running state

The toaster now uses the response detail when there is one, through the existing getApiErrorDetail (already used by ErrorAlert), and keeps error.message as the fallback. Every action that goes through the shared toaster (delete, clear, patch, trigger, pause, …) benefits. 403s are still left to the MutationCache handler.

Two hooks had worked around this on their own, and now use the shared helper:

  • useUpdateHITLDetail special-cased exactly this in Refuse Human-in-the-loop responses once the task stopped waiting #75645. Its test expected the bare response.error title key only on that branch; the shared helper translates the namespaced hitl:response.error, which renders the same text.
  • useClearTaskInstances showed the detail only when it contained "AirflowClearRunningTaskException", so other refusals such as "Cannot clear running task instances" still showed "Conflict".

On what gets shown: the text is rendered as a React text node, so no markup is interpreted. It is the same detail the browser already received and that ErrorAlert already displays. Database and Dag deserialization errors keep the statement redacted unless [api] expose_stacktrace is on, and validation errors show only loc and msg, never the submitted input.

Testing:

  • New errorHandling.test.ts, plus a clear-task test; both fail against the old code.
  • Full UI vitest suite, eslint, prettier, tsc, and prek (pre-commit and manual stages).
  • Manually in a local Airflow: deleting a running Dag run shows "Conflict" before and the reason after; clearing a running task with "Prevent rerun if task is running" still shows its reason.
    Before
pr-before After pr-after
Was generative AI tooling used to co-author this PR?
  • Yes — Claude Code (Opus 5.5)

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

createErrorToaster used error.message for the toast description. For an
API error that is only the HTTP status text, so a refused action showed
just "Conflict": deleting a running Dag run toasted "Error deleting Dag
Run / Conflict" while the API said the run cannot be deleted in running
state.

Use the response detail when the API sent one, through the existing
getApiErrorDetail helper, and keep error.message as the fallback. The
Human-in-the-loop response hook special-cased exactly this; it now uses
the shared helper.
The clear hook showed the response detail only when it contained
"AirflowClearRunningTaskException". Other refusals, such as "Cannot
clear running task instances" or the loop-aware 409s, still toasted
just "Conflict". Use the shared error toaster, which already shows the
detail and skips 403s.
@boring-cyborg boring-cyborg Bot added the area:UI Related to UI/UX. For Frontend Developers. label Nov 3, 2026

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

Labels

area:UI Related to UI/UX. For Frontend Developers.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant