Skip to content

Fix Dag processor API client not catching Task SDK client errors - #74728

Closed
ephraimbuddy wants to merge 3 commits into
apache:mainfrom
astronomer:dag-processor-api-client-httpx2
Closed

ephraimbuddy wants to merge 3 commits into
apache:mainfrom
astronomer:dag-processor-api-client-httpx2

Conversation

@ephraimbuddy

@ephraimbuddy ephraimbuddy commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

DagProcessorAPIClient (#74370) subclasses the Task SDK Client, which #73116 moved from httpx to httpx2. httpx2 has its own exception and transport classes, so the processor client's except httpx.HTTPStatusError / httpx.HTTPError / httpx.ResponseNotRead clauses stopped matching anything the client raises. Registration conflicts, closed-Job retirement and the early token-renewal fallbacks all fell through. On main, dag_processing/test_api_client.py and execution_api/test_dag_processor_client.py fail 86 of 98 tests, because httpx.MockTransport responses trip httpx2's SyncByteStream assertion.

  • Move the processor client and its two test modules to httpx2. The test changes are the mechanical httpx → httpx2 swap; no assertions changed.
  • Let the Task SDK's ServerResponseError.from_response accept JSON errors whose Content-Type carries parameters (application/json; charset=utf-8) or different letter case. Those were raised as a bare HTTPStatusError with the body unread, so the processor client could not see the error reason.
  • Ban httpx imports in airflow-core with ruff (TID251), as task-sdk already does, so the next mix-up is a lint error instead of failing tests. The six modules not yet migrated are exempt until Consider swapping httpx usage for httpx2 #70522 moves them (wider migration also in Migrate airflow-core to httpx2 #74244).

related: #70522


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

The Task SDK Client moved from httpx to httpx2 in apache#73116, and
httpx2's exception and transport classes are separate from httpx's.
DagProcessorAPIClient subclasses that Client but still caught httpx
exceptions, so registration conflicts, closed-Job retirement and early token
renewal fallbacks never matched the errors the client actually raises, and its
overrides were typed against the wrong Response, Timeout and Headers classes.

The rest of airflow-core stays on httpx until the wider migration tracked in
apache#74244 and apache#70522.
ServerResponseError.from_response compared the Content-Type header for exact
equality with application/json, so a JSON error sent as
"application/json; charset=utf-8", or in different letter case, was raised as a
bare HTTPStatusError with its body unread. Callers that inspect the error
detail, such as the Dag processor client deciding whether its registration was
retired or its Job closed, then found no reason and treated the response as an
unexplained failure.
The Dag processor API client kept catching httpx exceptions after the Task SDK
client moved to httpx2, and nothing at lint time flagged the mismatch. With
httpx banned, the next mix-up is a ruff error instead of failing tests; the
modules not yet migrated are exempt until apache#70522 moves them.
@ephraimbuddy
ephraimbuddy marked this pull request as ready for review October 14, 2026 13:04

@aaron-y-chen aaron-y-chen 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.

Thanks!

@zach-overflow zach-overflow mentioned this pull request Oct 14, 2026
1 task done
ephraimbuddy added a commit to astronomer/airflow that referenced this pull request Oct 15, 2026
The Task SDK client raises httpx2 exceptions and requires matching transport and timeout objects. Integrate apache#74728 so isolated processors can recover from API failures and retain structured error reasons, including in the manager and job runner paths introduced by this branch.
from uuid import UUID

import httpx
import httpx2

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.

This file and both test modules are already on main through #74731, which merged after this branch was last pushed. git diff between this head and main is empty for all three. After a rebase, what's left is the from_response media-type change, its test, and the ruff ban. Could you retitle around those? As it stands the squash commit would claim the #74731 fix a second time in the changelog, and the description's "fails 86 of 98 tests on main" no longer describes main.

return None

if response.headers.get("content-type") != "application/json":
media_type = response.headers.get("content-type", "").partition(";")[0].strip().lower()

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.

Which server sends application/json; charset=utf-8 here? FastAPI's JSONResponse sets plain application/json (Starlette only appends a charset for text/*), so I think this branch only fires behind a proxy that rewrites the content type. That's reasonable to tolerate, but it reaches every SDK client, not just the processor: a proxy-generated JSON 404/409/410 on a heartbeat now becomes a ServerResponseError and takes the immediate SIGTERM path in supervisor.py instead of going through _handle_heartbeat_failures. Could the description say that, rather than framing it as a processor-client fix?

@ephraimbuddy

Copy link
Copy Markdown
Contributor Author

closing as #74731 was merged ahead of this

@jedcunningham
jedcunningham deleted the dag-processor-api-client-httpx2 branch October 15, 2026 20:07
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.

3 participants