This is an automated email from the ASF dual-hosted git repository.
vatsrahul1001 pushed a commit to branch v3-3-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
commit e52d3993aedd5b1b30dee9d7eb303f61800f90c6
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Wed Aug 12 17:10:39 2026 +0200
[v3-3-test] Include server error detail in Task SDK API error tracebacks
(#71440) (#71491)
* Include server error detail in Task SDK API error tracebacks
Internal SDK API errors keep the server's error payload as structured detail
behind a generic "Server returned error" message, so the detail only
reached the
logs where a handler logged it explicitly. When such an error propagated
uncaught
to a generic logger (e.g. the executor), the traceback showed only the
generic
message and the detail was lost. Attaching the detail as an exception note
— like
the existing correlation-id note — makes it visible in those tracebacks too.
* Update task-sdk/src/airflow/sdk/api/client.py
* Update task-sdk/tests/task_sdk/api/test_client.py
* Fix over-indented test docstring breaking the static check
The docstring was indented one space too deep, so the following line
unindented to a level that doesn't match, failing the mypy syntax check.
---------
(cherry picked from commit ede958a38d025049f28b570d9e6629192c243b8d)
Co-authored-by: Pierre Jeambrun <[email protected]>
Co-authored-by: Amogh Desai <[email protected]>
---
task-sdk/src/airflow/sdk/api/client.py | 6 ++++++
task-sdk/tests/task_sdk/api/test_client.py | 17 +++++++++++++++++
2 files changed, 23 insertions(+)
diff --git a/task-sdk/src/airflow/sdk/api/client.py
b/task-sdk/src/airflow/sdk/api/client.py
index 8e0a46a477a..4353802c61c 100644
--- a/task-sdk/src/airflow/sdk/api/client.py
+++ b/task-sdk/src/airflow/sdk/api/client.py
@@ -211,6 +211,12 @@ def raise_on_4xx_5xx_with_note(response: httpx.Response):
e.add_note(
f"Correlation-id={response.headers.get('correlation-id', None) or
response.request.headers.get('correlation-id', 'no-correlation-id')}"
)
+ # .detail sits behind the generic message and only reaches logs where
a handler
+ # logs it by hand. Add it as a note too, so uncaught paths (e.g. the
executor) keep it.
+
+ detail = getattr(e, "detail", None)
+ if detail is not None:
+ e.add_note(f"Server error detail: {detail!r}")
raise
diff --git a/task-sdk/tests/task_sdk/api/test_client.py
b/task-sdk/tests/task_sdk/api/test_client.py
index a7f69e948f1..532589dfc2e 100644
--- a/task-sdk/tests/task_sdk/api/test_client.py
+++ b/task-sdk/tests/task_sdk/api/test_client.py
@@ -19,6 +19,7 @@ from __future__ import annotations
import json
import pickle
+import sys
from datetime import datetime, timezone as dt_timezone
from typing import TYPE_CHECKING
from unittest import mock
@@ -197,6 +198,22 @@ class TestClient:
assert unpickled.response.status_code == 404
assert unpickled.request.url == "http://error"
+ @pytest.mark.skipif(sys.version_info < (3, 11), reason="Exception notes
(PEP 678) require Python 3.11")
+ def test_server_error_detail_added_as_note(self):
+ """Notes survive uncaught propagation, handled sites still log detail
directly."""
+ responses = [httpx.Response(404, json={"detail": {"message": "Invalid
input"}})]
+ client = make_client_w_responses(responses)
+
+ with pytest.raises(ServerResponseError) as exc_info:
+ client.get("http://error")
+
+ err = exc_info.value
+ assert err.args == ("Server returned error",)
+ assert any(
+ note.startswith("Server error detail:") and "Invalid input" in note
+ for note in getattr(err, "__notes__", [])
+ ), err.__notes__
+
def test_retry_handling_unrecoverable_error(self):
with time_machine.travel("2023-01-01T00:00:00Z", tick=False):
responses: list[httpx.Response] = [