Skip to content

fix: update AirbyteTracedException.__str__ to show user-facing message (AI-Triage PR)#927

Open
devin-ai-integration[bot] wants to merge 5 commits intomainfrom
devin/1772482932-fix-traced-exception-str
Open

fix: update AirbyteTracedException.__str__ to show user-facing message (AI-Triage PR)#927
devin-ai-integration[bot] wants to merge 5 commits intomainfrom
devin/1772482932-fix-traced-exception-str

Conversation

@devin-ai-integration
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot commented Mar 2, 2026

fix: update AirbyteTracedException.str to show user-facing message

Summary

Moves the __str__ override from the temporary MessageRepresentationAirbyteTracedErrors subclass into the base AirbyteTracedException class, so that str(exception) returns the user-facing message globally (falling back to internal_message if message is not set).

The subclass MessageRepresentationAirbyteTracedErrors in http_client.py was an acknowledged workaround — its own docstring stated this behavior should eventually be promoted to the base class. This PR completes that migration.

Additionally, from_exception is updated to correctly preserve both internal_message and message when wrapping an existing AirbyteTracedException, rather than going through str(exc) which would now return the user-facing message instead of the internal details.

Changes:

  • airbyte_cdk/utils/traced_exception.py: Add __str__ that returns self.messageself.internal_message"" (using is not None checks to correctly handle message="")
  • airbyte_cdk/utils/traced_exception.py: Update from_exception to detect AirbyteTracedException instances and preserve both internal_message and message directly, instead of using str(exc) which would now return the user-facing message
  • airbyte_cdk/sources/streams/http/http_client.py: Remove MessageRepresentationAirbyteTracedErrors class; update two raise sites to use AirbyteTracedException directly
  • unit_tests/utils/test_traced_exception.py: Add 13 tests in TestAirbyteTracedExceptionStr + 8 tests in TestFromExceptionPreservesFields

Resolves https://github.com/airbytehq/airbyte-internal-issues/issues/15916

Updates since last revision

  • from_exception now preserves fields when wrapping AirbyteTracedException: Previously from_exception always set internal_message=str(exc). With the new __str__, this would put the user-facing message into internal_message, losing the actual debug info. Now when wrapping an AirbyteTracedException:
    • internal_message is taken directly from exc.internal_message (not via str())
    • message is preserved from the original unless the caller explicitly provides one
    • For non-AirbyteTracedException exceptions, behavior is unchanged (str(exc))
  • Added 8 new tests in TestFromExceptionPreservesFields covering: both fields set, only message, only internal_message, neither field, caller override, and regular exception wrapping
  • Prior revision: changed if self.message: to if self.message is not None: (and similarly for internal_message) so that message="" is respected. Added test for this edge case.

Review & Testing Checklist for Human

  • from_exception field preservation logic: The new isinstance check uses "message" not in kwargs to decide whether to carry over the original's message. Verify that all existing call sites pass message as a keyword argument (not positional via *args). Grep for .from_exception( across the codebase to confirm.
  • Blast radius of global __str__ change: This changes behavior for every AirbyteTracedException subclass across the CDK. Search for str(e), f"{e}", and logging calls that stringify caught AirbyteTracedException instances to verify no critical debug info is lost in logs or error tracking.
  • Stack trace formatting now uses user-facing message: Tests confirmed that TracebackException.format() calls __str__, so stack trace one-liners now show message instead of internal_message. Verify this doesn't disrupt Sentry grouping or log aggregation that keys on exception string representations.
  • args[0] vs __str__ mismatch: The constructor still passes internal_message to super().__init__(), so exception.args[0] remains internal_message while str(exception) now returns message. Confirm no code relies on args[0] == str(exception).
  • Empty-string message behavior change: With is not None checks, message="" now returns "" instead of falling back to internal_message. Verify that call sites setting message="" (e.g., job_orchestrator.py:484) intend for str(exc) to be empty rather than showing the internal message.

Suggested test plan: Run the full CDK test suite (poetry run poe pytest-fast). Additionally, grep the codebase for str(e), f"{e}", and %s patterns on AirbyteTracedException instances to manually verify the behavioral change is safe. Check Sentry dashboards after deployment to confirm error grouping is not disrupted.

Notes

Move the __str__ override from the MessageRepresentationAirbyteTracedErrors
subclass into the base AirbyteTracedException class so that str(exception)
returns the user-facing message globally, falling back to internal_message.

Remove the now-unnecessary MessageRepresentationAirbyteTracedErrors subclass
and update the two raise sites in HttpClient to use AirbyteTracedException
directly.

Co-Authored-By: bot_apk <apk@cognition.ai>
@devin-ai-integration
Copy link
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@github-actions
Copy link

github-actions bot commented Mar 2, 2026

👋 Greetings, Airbyte Team Member!

Here are some helpful tips and reminders for your convenience.

💡 Show Tips and Tricks

Testing This CDK Version

You can test this version of the CDK using the following:

# Run the CLI from this branch:
uvx 'git+https://github.com/airbytehq/airbyte-python-cdk.git@devin/1772482932-fix-traced-exception-str#egg=airbyte-python-cdk[dev]' --help

# Update a connector to use the CDK from this branch ref:
cd airbyte-integrations/connectors/source-example
poe use-cdk-branch devin/1772482932-fix-traced-exception-str

PR Slash Commands

Airbyte Maintainers can execute the following slash commands on your PR:

  • /autofix - Fixes most formatting and linting issues
  • /poetry-lock - Updates poetry.lock file
  • /test - Runs connector tests with the updated CDK
  • /prerelease - Triggers a prerelease publish with default arguments
  • /poe build - Regenerate git-committed build artifacts, such as the pydantic models which are generated from the manifest JSON schema in YAML.
  • /poe <command> - Runs any poe command in the CDK environment
📚 Show Repo Guidance

Helpful Resources

📝 Edit this welcome message.

@github-actions
Copy link

github-actions bot commented Mar 2, 2026

PyTest Results (Fast)

3 890 tests  +21   3 878 ✅ +21   6m 47s ⏱️ -4s
    1 suites ± 0      12 💤 ± 0 
    1 files   ± 0       0 ❌ ± 0 

Results for commit cdea7d6. ± Comparison against base commit 7f41401.

♻️ This comment has been updated with latest results.

@github-actions
Copy link

github-actions bot commented Mar 2, 2026

PyTest Results (Full)

3 893 tests   3 881 ✅  11m 26s ⏱️
    1 suites     12 💤
    1 files        0 ❌

Results for commit cdea7d6.

♻️ This comment has been updated with latest results.

@sophiecuiy
Copy link

/ai-prove-fix

Add 12 tests in TestAirbyteTracedExceptionStr covering:
- str() returns user-facing message when both message and internal_message set
- str() falls back to internal_message when message is None
- str() returns empty string when both are None
- str() works in f-strings and %-formatting
- args[0] still contains internal_message for backward compat
- Subclasses inherit the __str__ override
- from_exception() factory method works correctly
- Stack trace and internal_message preserved in trace error

Co-Authored-By: bot_apk <apk@cognition.ai>
@devin-ai-integration
Copy link
Contributor Author

Added 12 tests in TestAirbyteTracedExceptionStr proving the fix works:

Test What it proves
test_str_returns_user_facing_message_when_both_set str() returns message over internal_message
test_str_falls_back_to_internal_message_when_message_is_none Backward compat: falls back to internal_message
test_str_returns_empty_string_when_both_none Edge case: empty string when neither set
test_str_returns_message_when_internal_message_is_none Works with only message set
test_str_used_in_fstring_returns_user_facing_message f-string interpolation uses user-facing message
test_str_used_in_logging_format_returns_user_facing_message %s formatting uses user-facing message
test_args_still_contains_internal_message args[0] still holds internal_message
test_str_on_subclass_inherits_behavior Subclasses inherit __str__ without their own override
test_str_with_from_exception_factory from_exception() + message kwarg works
test_str_with_from_exception_without_message from_exception() without message falls back
test_stack_trace_uses_str_representation Stack trace one-liner uses __str__
test_internal_message_preserved_in_trace_error internal_message preserved in trace error object

All 22 tests pass locally (10 existing + 12 new).


🤖 Devin session

@sophiecuiy sophiecuiy marked this pull request as ready for review March 4, 2026 00:10
Copilot AI review requested due to automatic review settings March 4, 2026 00:10
Copy link
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

Promotes the user-facing string representation of AirbyteTracedException into the base class so str(exception) returns message (with fallback), and removes the temporary http_client.py workaround class.

Changes:

  • Add AirbyteTracedException.__str__ to prefer message over internal_message.
  • Remove MessageRepresentationAirbyteTracedErrors and raise AirbyteTracedException directly in HttpClient.
  • Add unit tests validating __str__ behavior across common construction paths and stack trace formatting.

Reviewed changes

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

File Description
airbyte_cdk/utils/traced_exception.py Adds global __str__ override to return user-facing message (with fallback).
airbyte_cdk/sources/streams/http/http_client.py Removes workaround subclass and updates two raise sites to use AirbyteTracedException.
unit_tests/utils/test_traced_exception.py Adds a new test suite covering the new __str__ semantics and related behaviors.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

)
airbyte_message = exc.as_airbyte_message()
assert airbyte_message.trace.error.internal_message == "raw API error: 401"
assert airbyte_message.trace.error.message == "Authentication failed."
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

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

Tests cover message=None but don’t cover the case where message is explicitly set to an empty string. Since the codebase uses message="" in at least one place, add a test asserting str(AirbyteTracedException(message="", internal_message="...") ) returns "" (and does not fall back to internal_message) to prevent regressions once __str__ is updated to distinguish empty string from None.

Suggested change
assert airbyte_message.trace.error.message == "Authentication failed."
assert airbyte_message.trace.error.message == "Authentication failed."
def test_str_with_empty_message_does_not_fall_back_to_internal_message(self) -> None:
"""Explicit empty message should be respected and not replaced by internal_message."""
exc = AirbyteTracedException(
internal_message="an internal error that should not be shown to the user",
message="",
)
assert str(exc) == ""

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added this test in 2dff2e5. Also switched the __str__ implementation to use is not None checks so message="" is properly respected.

Comment on lines +52 to +59
def __str__(self) -> str:
"""Return the user-facing message, falling back to internal_message."""

if self.message:
return self.message
elif self.internal_message:
return self.internal_message
return ""
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

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

__str__ currently checks if self.message: / elif self.internal_message:, which treats empty strings as “unset”. There is at least one existing call site that sets message="" intentionally; with this implementation str(exc) would fall back to the (potentially very large) internal_message instead of honoring the user-facing message. Consider checking explicitly for is not None (and similarly for internal_message) so message="" results in an empty string rather than leaking internal details.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch — updated to use is not None checks and added the empty string test. See 2dff2e5.

devin-ai-integration bot and others added 3 commits March 4, 2026 00:14
Address Copilot review: use explicit 'is not None' checks instead of
truthiness so that message='' is respected and does not fall back to
internal_message. Add test for this edge case.

Co-Authored-By: bot_apk <apk@cognition.ai>
…acedException

When wrapping an AirbyteTracedException, from_exception now:
- Takes internal_message directly from the original instead of via str(exc)
- Preserves the original's user-facing message if the caller doesn't provide one

This prevents the user-facing message from being lost or incorrectly
assigned to internal_message after the __str__ change.

Co-Authored-By: sophie.cui@airbyte.io <sophie.cui@airbyte.io>
Co-Authored-By: sophie.cui@airbyte.io <sophie.cui@airbyte.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants