Skip to content

Fix airflowctl tracebacks on redirects and non-JSON error bodies - #73307

Merged
shahar1 merged 1 commit into
apache:mainfrom
Eason09053360:fix-airflowctl-traceback-on-redirect-and-non-json-errors
Sep 18, 2026
Merged

shahar1 merged 1 commit into
apache:mainfrom
Eason09053360:fix-airflowctl-traceback-on-redirect-and-non-json-errors

Conversation

@Eason09053360

Copy link
Copy Markdown
Contributor

Why

safe_call_command in airflow-ctl/src/airflowctl/ctl/cli_config.py is the single place where an airflowctl failure becomes one actionable line instead of a traceback. It caught ServerResponseError, but the API client only builds that for a 4xx/5xx response whose body is JSON (ServerResponseError.from_response). For anything else the response hook falls through to response.raise_for_status(), which raises the bare httpx.HTTPStatusError that ServerResponseError subclasses — and a parent class is not caught by a child's except, so nothing handled it.

Two shapes of response land there, and both are cases where the operator has the least context to work with:

  • a 3xx, e.g. an SSO proxy in front of the API server answering with a redirect (the client does not follow redirects);
  • a 4xx/5xx whose body is not JSON, e.g. an ingress returning its own HTML error page.

Neither ever reached the Airflow API server, and a traceback says nothing about that.

Related: #72468 closes the same kind of gap on the other side of this function, for airflowctl's own exception family.

What

Adds an except httpx.HTTPStatusError clause to safe_call_command, printing the httpx message (which already names the status, the URL, and for a redirect its Location) and exiting 1 — the same exit code the uncaught traceback produced, so scripted callers are unaffected. A redirect additionally gets one line pointing at the configured API URL, since a 3xx does not read as an error the way a 502 does.

The clause has to sit below except ServerResponseError, or that branch becomes unreachable; the comment in the diff records why.

Tests in airflow-ctl/tests/airflow_ctl/ctl/test_cli_config.py cover a redirect and a non-JSON 4xx and 5xx, raising through a real response.raise_for_status() rather than a hand-built exception, and assert both that the redirect hint appears and that it does not appear for the other two.


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

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

airflowctl funnels every command through a single handler whose job is to
turn a failure into one line the operator can act on. That handler only
recognised ServerResponseError, which the API client builds solely for
4xx/5xx responses carrying a JSON body -- everything else leaves the
response hook raising the bare httpx.HTTPStatusError that
ServerResponseError subclasses, so no clause caught it.

The responses that fall outside that narrow definition are precisely the
ones an operator has least context for: an SSO proxy answering with a
redirect, or an ingress returning its own HTML error page. Those never
reached the Airflow API server at all, and a traceback tells the operator
nothing about why.
@shahar1
shahar1 merged commit 44094b2 into apache:main Sep 18, 2026
97 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

Backport successfully created: airflow-ctl/v0-1-test

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
✅ airflow-ctl/v0-1-test PR Link

github-actions Bot pushed a commit to aws-mwaa/upstream-to-airflow that referenced this pull request Sep 18, 2026
…n-JSON error bodies (apache#73307)

airflowctl funnels every command through a single handler whose job is to
turn a failure into one line the operator can act on. That handler only
recognised ServerResponseError, which the API client builds solely for
4xx/5xx responses carrying a JSON body -- everything else leaves the
response hook raising the bare httpx.HTTPStatusError that
ServerResponseError subclasses, so no clause caught it.

The responses that fall outside that narrow definition are precisely the
ones an operator has least context for: an SSO proxy answering with a
redirect, or an ingress returning its own HTML error page. Those never
reached the Airflow API server at all, and a traceback tells the operator
nothing about why.
(cherry picked from commit 44094b2)

Co-authored-by: Y-C <easoneason0905@gmail.com>
aws-airflow-bot pushed a commit to aws-mwaa/upstream-to-airflow that referenced this pull request Sep 18, 2026
…n-JSON error bodies (apache#73307)

airflowctl funnels every command through a single handler whose job is to
turn a failure into one line the operator can act on. That handler only
recognised ServerResponseError, which the API client builds solely for
4xx/5xx responses carrying a JSON body -- everything else leaves the
response hook raising the bare httpx.HTTPStatusError that
ServerResponseError subclasses, so no clause caught it.

The responses that fall outside that narrow definition are precisely the
ones an operator has least context for: an SSO proxy answering with a
redirect, or an ingress returning its own HTML error page. Those never
reached the Airflow API server at all, and a traceback tells the operator
nothing about why.
(cherry picked from commit 44094b2)

Co-authored-by: Y-C <easoneason0905@gmail.com>
potiuk pushed a commit that referenced this pull request Sep 22, 2026
…n-JSON error bodies (#73307) (#73331)

airflowctl funnels every command through a single handler whose job is to
turn a failure into one line the operator can act on. That handler only
recognised ServerResponseError, which the API client builds solely for
4xx/5xx responses carrying a JSON body -- everything else leaves the
response hook raising the bare httpx.HTTPStatusError that
ServerResponseError subclasses, so no clause caught it.

The responses that fall outside that narrow definition are precisely the
ones an operator has least context for: an SSO proxy answering with a
redirect, or an ingress returning its own HTML error page. Those never
reached the Airflow API server at all, and a traceback tells the operator
nothing about why.
(cherry picked from commit 44094b2)

Co-authored-by: Y-C <easoneason0905@gmail.com>
rjgoyln added a commit to rjgoyln/airflow that referenced this pull request Oct 23, 2026
Since apache#73307 airflowctl catches the bare HTTPStatusError and prints one
line instead of a traceback, so the newsfragment overstated it; the
server's own message is still what gets lost.
henry3260 added a commit that referenced this pull request Oct 25, 2026
* Fix airflowctl tracebacks on JSON errors behind a reverse proxy

airflowctl compared the error response content-type against the exact
string "application/json". Any reverse proxy, ingress, or API gateway
that returns "application/json; charset=utf-8" — or emits its own
"application/problem+json" error — fell outside that comparison, so
ServerResponseError was never constructed and the friendly "not found"
messages in the dags and tasks commands were skipped. The bare
httpx.HTTPStatusError raised instead is not handled by
safe_call_command, so users saw a raw traceback. Airflow's own API
server does not send a charset, which is why this only surfaces once
something sits in front of it.

Media types are case-insensitive and may carry parameters (RFC 9110),
so the header has to be parsed rather than compared whole.

Matching more responses also routes bodies into the error path that are
valid JSON but not objects, which dict() rejected with a ValueError from
that same unprotected path, and bodies that do not decode at all.

* Move media type rationale from docstring into an inline comment

* Apply the same JSON media type handling to the Task SDK client

The Task SDK client carries the identical exact-string content-type
comparison, so a task talking to the API server through a proxy that
declares a charset loses the server's error detail and, because the
raised exception is then a plain httpx.HTTPStatusError, no longer hits
the status-specific handling in the supervisor and the task runner.

* Cross-reference the twin JSON media type checks

The airflowctl and Task SDK copies are the same three lines with
different comments, and they already drifted in the commits that
created them. A pointer in each makes the next person notice there
are two.

* Add newsfragment for the API client error handling fix

Requested during review: the Task SDK half of the change ships in
airflow-core, and both effects are visible to users running behind a
proxy.

* Use httpx2 in the Task SDK JSON media type check

The Task SDK moved from httpx to httpx2 (#73116) after this branch was
written, so the rebased helper and its tests referred to a module that
is no longer imported there.

* Describe the airflowctl symptom as it is on main now

Since #73307 airflowctl catches the bare HTTPStatusError and prints one
line instead of a traceback, so the newsfragment overstated it; the
server's own message is still what gets lost.

* Keep only the airflowctl test cases the fix changes

The bare media type and the 404/500 axis repeat what
test_error_parsing_with_unread_body already covers, and dict() of an
object body worked before; none of them fail without the fix.

* Explain the non-object log guard by what it protects

The previous comment described the dict() call the guard replaced,
which is no longer in the code.

* Limit the newsfragment to the Task SDK fix

airflow-core's release notes ship only the Task SDK side; airflowctl's
changelog is built from git log. The task runner never handled
ServerResponseError, so it is no longer named.

* Rename the newsfragment to this PR's number

* Shorten the media type comments

The longer versions restated what the diff and the PR already explain,
and the two _has_json_body copies now carry the same note.

---------

Co-authored-by: Henry Chen <henrychen@apache.org>
potiuk pushed a commit that referenced this pull request Oct 25, 2026
…reverse proxy (#75493) (#75622)

* Fix airflowctl tracebacks on JSON errors behind a reverse proxy

airflowctl compared the error response content-type against the exact
string "application/json". Any reverse proxy, ingress, or API gateway
that returns "application/json; charset=utf-8" — or emits its own
"application/problem+json" error — fell outside that comparison, so
ServerResponseError was never constructed and the friendly "not found"
messages in the dags and tasks commands were skipped. The bare
httpx.HTTPStatusError raised instead is not handled by
safe_call_command, so users saw a raw traceback. Airflow's own API
server does not send a charset, which is why this only surfaces once
something sits in front of it.

Media types are case-insensitive and may carry parameters (RFC 9110),
so the header has to be parsed rather than compared whole.

Matching more responses also routes bodies into the error path that are
valid JSON but not objects, which dict() rejected with a ValueError from
that same unprotected path, and bodies that do not decode at all.

* Move media type rationale from docstring into an inline comment

* Apply the same JSON media type handling to the Task SDK client

The Task SDK client carries the identical exact-string content-type
comparison, so a task talking to the API server through a proxy that
declares a charset loses the server's error detail and, because the
raised exception is then a plain httpx.HTTPStatusError, no longer hits
the status-specific handling in the supervisor and the task runner.

* Cross-reference the twin JSON media type checks

The airflowctl and Task SDK copies are the same three lines with
different comments, and they already drifted in the commits that
created them. A pointer in each makes the next person notice there
are two.

* Add newsfragment for the API client error handling fix

Requested during review: the Task SDK half of the change ships in
airflow-core, and both effects are visible to users running behind a
proxy.

* Use httpx2 in the Task SDK JSON media type check

The Task SDK moved from httpx to httpx2 (#73116) after this branch was
written, so the rebased helper and its tests referred to a module that
is no longer imported there.

* Describe the airflowctl symptom as it is on main now

Since #73307 airflowctl catches the bare HTTPStatusError and prints one
line instead of a traceback, so the newsfragment overstated it; the
server's own message is still what gets lost.

* Keep only the airflowctl test cases the fix changes

The bare media type and the 404/500 axis repeat what
test_error_parsing_with_unread_body already covers, and dict() of an
object body worked before; none of them fail without the fix.

* Explain the non-object log guard by what it protects

The previous comment described the dict() call the guard replaced,
which is no longer in the code.

* Limit the newsfragment to the Task SDK fix

airflow-core's release notes ship only the Task SDK side; airflowctl's
changelog is built from git log. The task runner never handled
ServerResponseError, so it is no longer named.

* Rename the newsfragment to this PR's number

* Shorten the media type comments

The longer versions restated what the diff and the PR already explain,
and the two _has_json_body copies now carry the same note.

---------
(cherry picked from commit 352ca60)

Co-authored-by: rjgoyln <151457491+rjgoyln@users.noreply.github.com>
Co-authored-by: Henry Chen <henrychen@apache.org>
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.

2 participants