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] = [

Reply via email to