Skip to content

Fix API client errors losing detail behind a reverse proxy - #70936

Closed
rjgoyln wants to merge 5 commits into
apache:mainfrom
rjgoyln:ctl-json-media-type-errors
Closed

rjgoyln wants to merge 5 commits into
apache:mainfrom
rjgoyln:ctl-json-media-type-errors

Conversation

@rjgoyln

@rjgoyln rjgoyln commented Aug 2, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Both API clients recognised an error body only when the response content-type was exactly application/json, so a proxy that rewrites the header to application/json; charset=utf-8 skipped error parsing altogether: airflowctl printed a raw httpx.HTTPStatusError traceback in place of the server's message, and in the Task SDK the exception was no longer a ServerResponseError, so the status-specific handling in the supervisor and the task runner stopped matching. Airflow's own API server sends the bare media type, so this needs something sitting between client and server.

Change

Two adjacent airflowctl crashes go with it: a non-object JSON error body made log.warning(extra=...) raise, and a body labelled JSON that does not decode raised ValueError instead of falling through to raise_for_status().


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

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

@SameerMesiah97 SameerMesiah97 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just one nit.

Comment thread airflow-ctl/src/airflowctl/api/operations.py Outdated
@rjgoyln rjgoyln changed the title Fix airflowctl tracebacks on JSON errors behind a reverse proxy Fix API client errors losing detail behind a reverse proxy Aug 5, 2026
@potiuk potiuk added the ready for maintainer review Set after triaging when all criteria pass. label Aug 13, 2026

@potiuk potiuk left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving — solid fix, and the tests are better than most bug fixes get: parametrizing the media-type variants and pinning application/jsonp as a negative is exactly the trap a looser startswith check would have fallen into.

I went looking for two ways this could have been incomplete, and both came back clean:

  • Widening the media-type match in the Task SDK without adding the decode guard there. Not a problem — from_response already wraps parsing in try/except with an msgspec fallback and returns None (task-sdk/src/airflow/sdk/api/client.py:1372-1377). The explicit guard went into the one place that genuinely lacked one.
  • Passing a server-supplied dict straight into extra=. Safe here, but worth saying why I checked: airflowctl/api/client.py:82 is structlog.get_logger() and nothing in the distribution calls structlog.configure(), so extra is just an event key. If that logger were routed through stdlib logging, a proxy error page like {"message": "Bad Gateway"} would still raise KeyError: "Attempt to overwrite 'message' in LogRecord" — the same class of bug, from the same source. Something to keep in mind if airflowctl's logging setup ever changes.

One nit inline, and two things before this can land.

Please add a newsfragment

airflow-core/newsfragments/70936.bugfix.rst — the diff touches task-sdk/, and those changes ship in airflow-core. Both effects are user-visible: airflowctl printing a raw httpx.HTTPStatusError traceback instead of the server's message, and the supervisor / task runner's status-specific handling silently not matching behind a proxy. Defaulting to omitting one was the right call on your side — this is the maintainer asking for it.

Please rebase before this merges

The branch is 996 commits behind main and the last commit is from 2026-08-06, so the green CI on this PR was run against a tree that has moved a long way since. I'm approving on the strength of reading the diff, not on that run — please rebase (or use the update-branch button) so real CI executes against current main. I'll merge once it comes back green; the newsfragment can ride along in the same push.


This review was drafted by an AI-assisted tool and confirmed by an Airflow maintainer. The maintainer approving this PR has read the findings and signed off. If something feels off, please reply on the PR and a maintainer will follow up.

More on how Airflow handles maintainer review: contributing-docs/05_pull_requests.rst.


Drafted-by: Claude Code (Opus 5); reviewed by @potiuk before posting

Comment thread task-sdk/src/airflow/sdk/api/client.py
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.
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.
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.
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.
@rjgoyln
rjgoyln force-pushed the ctl-json-media-type-errors branch from ae1b2eb to ab5f552 Compare September 22, 2026 14:56
@potiuk potiuk added the closed because of open PR limit Closed as a one-time step of introducing the open pull request limit label Sep 25, 2026
@potiuk

potiuk commented Sep 25, 2026

Copy link
Copy Markdown
Member

Hello @rjgoyln - thank you for your contributions to Apache Airflow!

The Airflow community has introduced a limit of 5 open pull requests at a time for contributors without write access to the repository. You currently have 24 open pull requests, so - as a one-time step of introducing the limit - we closed the ones where maintainers have not engaged yet:

These pull requests stay open because maintainers are already engaged in them - they count towards your limit:

This is not a judgement of you or of your changes. We never told contributors before that opening many pull requests at once was a problem, so there is nothing to feel bad about - and nothing is lost: your branches, commits and the review history stay where they are.

What we ask you to do is to make your first prioritization decision: choose which of the pull requests above matter most to you, and reopen them (up to 5 open at a time, including the ones still open) with the "Reopen pull request" button or gh pr reopen <PR_NUMBER> --repo apache/airflow. Reopen the ones you are ready to follow through - keep them rebased, respond to review comments and fix failing checks.

While your pull requests are waiting for review, the most valuable thing you can do is help in other ways - reviewing other contributors' pull requests, helping with issues, and taking part in the discussions on the devlist and Slack.

Why we introduced the limit, what it means for you and how to reopen or restore a pull request is explained in https://github.com/apache/airflow/blob/main/contributing-docs/32_open_pull_request_limit.rst.


Drafted-by: Claude Code (Opus 5); reviewed by @potiuk before posting

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

Labels

area:airflow-ctl backport-to-airflow-ctl/v0-1-test closed because of open PR limit Closed as a one-time step of introducing the open pull request limit ready for maintainer review Set after triaging when all criteria pass.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants