Skip to content

fix(task-sdk): include error details in ServerResponseError string representation#63368

Open
YoannAbriel wants to merge 3 commits into
apache:mainfrom
YoannAbriel:fix/issue-57961
Open

fix(task-sdk): include error details in ServerResponseError string representation#63368
YoannAbriel wants to merge 3 commits into
apache:mainfrom
YoannAbriel:fix/issue-57961

Conversation

@YoannAbriel
Copy link
Copy Markdown
Contributor

Problem

When ServerResponseError is raised and propagates up the call stack without being explicitly caught and handled, error details are lost. Logs show only:

airflow.sdk.api.client.ServerResponseError: Server returned error

...without any information about what actually went wrong. This makes debugging significantly harder, especially for TaskInstanceOperations calls where the exception is not caught and re-logged with e.detail.

Root Cause

ServerResponseError stores error details in its detail attribute, but the __str__() method (inherited from httpx.HTTPStatusError) only returns the base message. When the exception is logged via traceback or str(err), the detail is silently dropped.

Some call sites (e.g., VariableOperations) explicitly catch ServerResponseError and log e.detail, but many others (e.g., TaskInstanceOperations.start()) let it propagate, losing the detail entirely.

Fix

Added a __str__() override to ServerResponseError that appends the detail attribute when present. This ensures error details are always visible in logs and tracebacks, regardless of how the exception is caught.

Before:

ServerResponseError: Server returned error

After:

ServerResponseError: Server returned error
Detail: {'reason': 'invalid_state', 'message': 'TI was not in the running state'}

Verified with unit tests. No external service access needed.

Closes: #57961


Was generative AI tooling used to co-author this PR?
  • Yes — Claude Code (Opus 4, claude-opus-4-6)

Generated-by: Claude Code (Opus 4, claude-opus-4-6) following the guidelines


@YoannAbriel YoannAbriel force-pushed the fix/issue-57961 branch 4 times, most recently from af40481 to 92a5e30 Compare March 14, 2026 13:06
@eladkal eladkal requested a review from sunank200 March 15, 2026 12:15
@eladkal eladkal added this to the Airflow 3.1.9 milestone Mar 15, 2026
Copy link
Copy Markdown
Contributor

@SameerMesiah97 SameerMesiah97 left a comment

Choose a reason for hiding this comment

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

Needs some adjustments. Please see my comments.

Comment thread task-sdk/src/airflow/sdk/api/client.py Outdated

def __str__(self) -> str:
base = super().__str__()
if self.detail:
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.

Right now def __str__ assumes that the detail attribute is always present in ServerResponseError. As evidenced by failing CI, this is not always true. Try adding doing something like this instead:

def __str__(self) -> str:
    base = super().__str__()
    detail = getattr(self, "detail", None)

    if detail is not None:
        return f"{base}\nDetail: {detail}"

    return base

Using if detail is not None instead of if self.detail or if not self.detail has the added benefit of including falsy values like {} or "", which might be considered meaningful API messages.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed — using getattr(self, 'detail', None) now.

assert "Detail:" in error_str
assert "invalid_state" in error_str

def test_server_response_error_str_without_detail(self):
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.

This test appears to covering scenarios where detail exists but is None. I would add another test for cases where the detail attribute does not exist. I would ensure that both of these tests (the current one for detail = None and the new one for detail not present which you may add) communicate their intent precisely in their naming, docstrings and comments.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added test_server_response_error_str_missing_detail_attr that creates a bare instance without the attribute.

@YoannAbriel YoannAbriel force-pushed the fix/issue-57961 branch 4 times, most recently from abba1a1 to 6137ca4 Compare March 16, 2026 19:04
@YoannAbriel YoannAbriel force-pushed the fix/issue-57961 branch 3 times, most recently from f58495e to f6f8cac Compare March 23, 2026 19:06
@potiuk
Copy link
Copy Markdown
Member

potiuk commented Apr 2, 2026

@YoannAbriel A few things need addressing before review — see our Pull Request quality criteria.

  • Pre-commit / static checks: Failing: CI image checks / Static checks. Run prek run --from-ref main locally to find and fix issues. See Pre-commit / static checks docs.

Note: Your branch is 251 commits behind main. Please rebase and push again to get up-to-date CI results.

No rush.


Note: This comment was drafted by an AI-assisted triage tool and may contain mistakes. Once you have addressed the points above, an Apache Airflow maintainer — a real person — will take the next look at your PR. We use this two-stage triage process so that our maintainers' limited time is spent where it matters most: the conversation with you.

@YoannAbriel YoannAbriel force-pushed the fix/issue-57961 branch 2 times, most recently from bb5abb3 to 36028f3 Compare April 5, 2026 12:01
@eladkal eladkal added backport-to-v3-2-test Mark PR with this label to backport to v3-2-test branch and removed backport-to-v3-1-test labels Apr 6, 2026
@eladkal eladkal modified the milestones: Airflow 3.1.9, Airflow 3.2.1 Apr 6, 2026
@YoannAbriel YoannAbriel force-pushed the fix/issue-57961 branch 2 times, most recently from fd85240 to 367b52c Compare April 6, 2026 15:01
@potiuk potiuk added the ready for maintainer review Set after triaging when all criteria pass. label Apr 6, 2026
@YoannAbriel YoannAbriel force-pushed the fix/issue-57961 branch 10 times, most recently from 6192734 to ba32e12 Compare April 10, 2026 09:04
@kaxil kaxil requested a review from Copilot April 10, 2026 19:55
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Improves debuggability of ServerResponseError by ensuring its string representation includes server-provided error details when available, so tracebacks/logs retain actionable context.

Changes:

  • Override ServerResponseError.__str__() to append detail to the base message.
  • Add unit tests covering __str__() behavior with dict detail, string detail, and missing detail attribute.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
task-sdk/src/airflow/sdk/api/client.py Adds __str__() override to include detail in the exception string.
task-sdk/tests/task_sdk/api/test_client.py Adds tests validating the new __str__() behavior across detail variants.


def __str__(self) -> str:
base = super().__str__()
if detail := getattr(self, "detail", None):
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

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

The if detail := ... check is truthiness-based, so empty-but-present details (e.g., {} / []) will be omitted even though detail exists. If the intent is to include details whenever the attribute is present (and not None), assign first and check detail is not None (or alternatively check hasattr(self, 'detail') and self.detail is not None).

Suggested change
if detail := getattr(self, "detail", None):
detail = getattr(self, "detail", None)
if detail is not None:

Copilot uses AI. Check for mistakes.
Comment on lines +212 to +214
err = ServerResponseError.__new__(ServerResponseError)
err.args = ("Server returned error",)

Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

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

This test relies on constructing ServerResponseError via __new__ without initializing the underlying httpx.HTTPStatusError state. That makes the test brittle against future httpx changes (e.g., if HTTPStatusError.__str__() starts requiring request/response). A more robust approach is to create a real ServerResponseError instance with minimal request/response, then remove/clear the detail attribute (e.g., delete it from __dict__) and assert str(err) does not include the Detail: section.

Suggested change
err = ServerResponseError.__new__(ServerResponseError)
err.args = ("Server returned error",)
responses = [
httpx.Response(
409,
json={"detail": {"reason": "invalid_state", "message": "TI was not in the running state"}},
)
]
client = make_client_w_responses(responses)
with pytest.raises(ServerResponseError) as exc_info:
client.get("http://error")
err = exc_info.value
if "detail" in err.__dict__:
del err.__dict__["detail"]

Copilot uses AI. Check for mistakes.
@YoannAbriel YoannAbriel force-pushed the fix/issue-57961 branch 3 times, most recently from 3c58f24 to 23f0a55 Compare April 13, 2026 06:06
…presentation

When ServerResponseError propagated up the stack without being
explicitly caught and handled, the error details were lost because
__str__() only returned the base message without the detail attribute.

This made debugging difficult as logs would show 'Server returned error'
without any context about what actually went wrong.

Added __str__() override to include the detail when present, so error
details are always visible in logs and tracebacks regardless of how the
exception is caught and re-raised.

Closes: apache#57961
@vatsrahul1001
Copy link
Copy Markdown
Contributor

@YoannAbriel can you address and resolve all open comments

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

Labels

area:task-sdk backport-to-v3-2-test Mark PR with this label to backport to v3-2-test branch ready for maintainer review Set after triaging when all criteria pass. type:bug-fix Changelog: Bug Fixes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Error details missing in the logs for internal SDK API calls

6 participants