Skip to content

fix: improve HTTP error messages with source attribution and stream context#918

Closed
Patrick Nilan (pnilan) wants to merge 1 commit intomainfrom
pnilan/fix-error-messages
Closed

fix: improve HTTP error messages with source attribution and stream context#918
Patrick Nilan (pnilan) wants to merge 1 commit intomainfrom
pnilan/fix-error-messages

Conversation

@pnilan
Copy link
Contributor

@pnilan Patrick Nilan (pnilan) commented Feb 24, 2026

Summary

  • Updates all default HTTP error mapping messages to clearly attribute errors to the source's API (not Airbyte platform)
  • Adds _format_error_message() to HttpClient that injects stream name and HTTP status code into user-facing messages (e.g. Stream 'users': HTTP 403. Source's API denied access.)
  • Updates the default fallback connector error message from "Something went wrong in the connector" to "Unhandled connector error. See logs for details."
  • Updates all related test assertions

Error message changes

Code Before After
400 HTTP Status Code: 400. Bad request. Bad request response from source's API.
401 HTTP Status Code: 401. Unauthorized... Authentication failed on source's API. Credentials may be invalid, expired, or lack required access.
403 HTTP Status Code: 403. Forbidden... Source's API denied access. Configured credentials have insufficient permissions.
404 HTTP Status Code: 404. Requested resource not found. Requested resource not found on source's API.
405 HTTP Status Code: 405. Method not allowed. Method not allowed by source's API.
408 HTTP Status Code: 408. Request timeout. Request to source's API timed out.
429 HTTP Status Code: 429. Rate limit exceeded. Rate limit exceeded on source's API.
500 HTTP Status Code: 500. Internal server error. Internal server error from source's API.
502 HTTP Status Code: 502. Bad gateway. Bad gateway response from source's API.
503 HTTP Status Code: 503. Service unavailable. Source's API is temporarily unavailable.
504 HTTP Status Code: 504. Gateway timeout. Gateway timeout from source's API.

Test plan

  • Unit tests updated for all changed error strings
  • Verify _format_error_message correctly prepends stream name and HTTP status

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Enhanced HTTP error messages to provide clearer, source-specific context (e.g., "Bad request response from source's API," "Source's API denied access").
    • Improved generic connector error messages to be more informative and actionable for end-users.

…and stream context

Default HTTP error messages now clearly attribute errors to the source's API
(not Airbyte platform), and HttpClient injects the stream name and HTTP status
code for user-facing context. Example: "Stream 'users': HTTP 403. Source's API
denied access. Configured credentials have insufficient permissions."

Also fixes the default fallback connector error message to be more specific.
Copilot AI review requested due to automatic review settings February 24, 2026 23:12
@github-actions
Copy link

👋 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@pnilan/fix-error-messages#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 pnilan/fix-error-messages

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.

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

This pull request improves HTTP error messages throughout the Airbyte CDK by clarifying that errors originate from the source's API (not Airbyte) and adding stream context to make debugging easier. The changes update default error mappings, introduce a new formatting method to prepend stream names and HTTP status codes, and update the generic connector error fallback message.

Changes:

  • Updated all HTTP status code error messages (400-504) in the default error mapping to clearly attribute errors to "source's API" rather than being ambiguous about the error origin
  • Added _format_error_message() method to HttpClient that prepends stream name and HTTP status code context to error messages
  • Changed the default connector error fallback from "Something went wrong in the connector. See the logs for more details." to "Unhandled connector error. See logs for details."

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 1 comment.

File Description
airbyte_cdk/sources/streams/http/error_handlers/default_error_mapping.py Updated all HTTP status code error messages to attribute errors to source's API instead of being ambiguous
airbyte_cdk/sources/streams/http/http_client.py Added _format_error_message() method and integrated it into error handling to prepend stream name and HTTP status context
airbyte_cdk/utils/traced_exception.py Updated default fallback error message to be more concise
unit_tests/**/*.py Updated all test assertions to match new error message formats

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

Comment on lines +427 to +432
def _format_error_message(self, error_resolution: ErrorResolution, response: Optional[requests.Response]) -> Optional[str]:
"""Prepend stream name and HTTP status code to the error resolution message for user-facing context."""
if not error_resolution.error_message:
return None
status_prefix = f"HTTP {response.status_code}. " if response is not None else ""
return f"Stream '{self._name}': {status_prefix}{error_resolution.error_message}"
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

The new _format_error_message method lacks dedicated unit tests. While existing tests will implicitly verify that the formatted messages contain the expected substrings, there are no tests that explicitly verify the stream name and HTTP status code are correctly prepended to error messages. Consider adding unit tests specifically for this method to verify its behavior with and without a response object, and to ensure the formatting is correct.

Copilot uses AI. Check for mistakes.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 24, 2026

📝 Walkthrough

Walkthrough

The PR refines HTTP error messaging throughout the Airbyte CDK by introducing a helper function to enrich error messages with stream context and HTTP status codes, updating default error messages for better user clarity, and aligning test expectations with these changes.

Changes

Cohort / File(s) Summary
Error Message Infrastructure
airbyte_cdk/sources/streams/http/error_handlers/default_error_mapping.py
Updated error_message strings for HTTP status codes (400, 401, 403, 404, 405, 408, 429, 500, 502, 503, 504) to be more descriptive and source-API-focused, e.g., "Bad request response from source's API." No functional or control flow changes.
Error Handling and Message Enrichment
airbyte_cdk/sources/streams/http/http_client.py
Introduces _format_error_message helper to enrich error messages with stream name and HTTP status code. Applies this helper across multiple error-handling paths (FAIL, IGNORE, RETRY actions) to provide better user-facing context. Minor change to exhausted retry message for brevity.
Generic Exception Handling
airbyte_cdk/utils/traced_exception.py
Updated default error message for unhandled connector errors from "Something went wrong in the connector. See the logs for more details." to "Unhandled connector error. See logs for details."
Test Updates – Check and Error Handler Tests
unit_tests/sources/declarative/checks/test_check_dynamic_stream.py, unit_tests/sources/declarative/checks/test_check_stream.py, unit_tests/sources/declarative/requesters/error_handlers/test_default_error_handler.py, unit_tests/sources/declarative/requesters/error_handlers/test_http_response_filter.py
Updated expected error message strings across multiple HTTP status code scenarios (400, 401, 403, 404, 429) to match the new descriptive format, e.g., "Requested resource not found on source's API." No logic changes.
Test Updates – HTTP Status and Availability Tests
unit_tests/sources/streams/http/error_handlers/test_http_status_error_handler.py, unit_tests/sources/streams/http/test_availability_strategy.py, unit_tests/sources/streams/http/test_http.py
Updated test expectations for HTTP error messages and adjusted assertions to verify error message content rather than rigid tuples. Changes align with new error messaging format.
Test Updates – Exception and Source Tests
unit_tests/sources/test_abstract_source.py, unit_tests/utils/test_traced_exception.py
Updated expected messages for generic connector errors to match the new "Unhandled connector error. See logs for details." text in both abstract source and traced exception test cases.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 36.36% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main objective: improving HTTP error messages by adding source attribution and stream context.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch pnilan/fix-error-messages

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
airbyte_cdk/sources/streams/http/error_handlers/default_error_mapping.py (1)

16-30: The exception-based mappings still use the old verbose style — wdyt about aligning them too?

The three exception-type entries (InvalidSchema, InvalidURL, RequestException) retain the legacy "Exception: requests.exceptions.XXX" suffix, while every HTTP-status entry was updated to the shorter, source/API-focused style. If the goal is consistent user-facing messaging, could these three be updated in the same pass?

✏️ Suggested alignment
 InvalidSchema: ErrorResolution(
     response_action=ResponseAction.FAIL,
     failure_type=FailureType.config_error,
-    error_message="Invalid Protocol Schema: The endpoint that data is being requested from is using an invalid or insecure. Exception: requests.exceptions.InvalidSchema",
+    error_message="Invalid or insecure protocol schema in the source's endpoint URL.",
 ),
 InvalidURL: ErrorResolution(
     response_action=ResponseAction.RETRY,
     failure_type=FailureType.transient_error,
-    error_message="Invalid URL specified or DNS error occurred: The endpoint that data is being requested from is not a valid URL. Exception: requests.exceptions.InvalidURL",
+    error_message="Invalid URL or DNS error for the source's endpoint.",
 ),
 RequestException: ErrorResolution(
     response_action=ResponseAction.RETRY,
     failure_type=FailureType.transient_error,
-    error_message="An exception occurred when making the request. Exception: requests.exceptions.RequestException",
+    error_message="An exception occurred when making the request to source's API.",
 ),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@airbyte_cdk/sources/streams/http/error_handlers/default_error_mapping.py`
around lines 16 - 30, Update the three exception mappings (InvalidSchema,
InvalidURL, RequestException) in default_error_mapping.py to use the shorter,
API/source-focused error_message style like the HTTP-status entries: edit the
ErrorResolution instances for InvalidSchema, InvalidURL, and RequestException so
their error_message strings remove the legacy "Exception:
requests.exceptions.XXX" suffix and instead give a concise, user-facing
description (keeping response_action and failure_type as-is) so messages are
consistent across exception- and status-based mappings.
unit_tests/sources/streams/http/test_availability_strategy.py (1)

108-110: Could we assert the new stream/status prefix here too, wdyt?

Line 108-Line 110 currently validates the mapped message, but not the newly introduced formatted context (Stream ... + HTTP 404.). Adding that check would better guard _format_error_message() behavior.

🧪 Possible assertion hardening
-    assert "Requested resource not found on source's API." in reason
+    assert reason is not None
+    assert reason.startswith("Stream '")
+    assert "HTTP 404." in reason
+    assert "Requested resource not found on source's API." in reason
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@unit_tests/sources/streams/http/test_availability_strategy.py` around lines
108 - 110, The test currently asserts only the mapped message; update the
assertion after calling
HttpAvailabilityStrategy().check_availability(http_stream, logger) to also
verify the formatted context produced by _format_error_message — specifically
assert that reason includes the "Stream <stream.name> reported HTTP 404." (or
the literal "Stream" prefix plus "HTTP 404.") alongside "Requested resource not
found on source's API."; locate the test using is_available, reason,
http_stream, logger and add the extra assertion checking the new stream/status
prefix to harden the expectation.
unit_tests/sources/declarative/checks/test_check_stream.py (1)

524-615: Would you consider extracting these repeated expected messages into shared constants, wdyt?

It could reduce duplication and make future wording changes less error-prone in this parametrized block.

♻️ Small DRY refactor idea
+NOT_FOUND_MSG = "Requested resource not found on source's API."
+FORBIDDEN_MSG = "Source's API denied access. Configured credentials have insufficient permissions."
+UNAUTHORIZED_MSG = "Authentication failed on source's API. Credentials may be invalid, expired, or lack required access."
...
-            ["Requested resource not found on source's API."],
+            [NOT_FOUND_MSG],
...
-            ["Source's API denied access. Configured credentials have insufficient permissions."],
+            [FORBIDDEN_MSG],
...
-            ["Authentication failed on source's API. Credentials may be invalid, expired, or lack required access."],
+            [UNAUTHORIZED_MSG],
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@unit_tests/sources/declarative/checks/test_check_stream.py` around lines 524
- 615, Extract the repeated expected message strings into module-level constants
(e.g., EXPECTED_MSG_RESOURCE_NOT_FOUND, EXPECTED_MSG_ACCESS_DENIED,
EXPECTED_MSG_AUTH_FAILED) in
unit_tests/sources/declarative/checks/test_check_stream.py and replace the
literal strings inside the pytest.param entries (the repeated messages in the
parametrized CheckStream cases) with those constants; ensure the constants'
names are clear and reuse them for both single-stream and
dynamic_streams_check_configs cases so future wording changes require only one
edit.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@airbyte_cdk/sources/streams/http/error_handlers/default_error_mapping.py`:
- Around line 16-30: Update the three exception mappings (InvalidSchema,
InvalidURL, RequestException) in default_error_mapping.py to use the shorter,
API/source-focused error_message style like the HTTP-status entries: edit the
ErrorResolution instances for InvalidSchema, InvalidURL, and RequestException so
their error_message strings remove the legacy "Exception:
requests.exceptions.XXX" suffix and instead give a concise, user-facing
description (keeping response_action and failure_type as-is) so messages are
consistent across exception- and status-based mappings.

In `@unit_tests/sources/declarative/checks/test_check_stream.py`:
- Around line 524-615: Extract the repeated expected message strings into
module-level constants (e.g., EXPECTED_MSG_RESOURCE_NOT_FOUND,
EXPECTED_MSG_ACCESS_DENIED, EXPECTED_MSG_AUTH_FAILED) in
unit_tests/sources/declarative/checks/test_check_stream.py and replace the
literal strings inside the pytest.param entries (the repeated messages in the
parametrized CheckStream cases) with those constants; ensure the constants'
names are clear and reuse them for both single-stream and
dynamic_streams_check_configs cases so future wording changes require only one
edit.

In `@unit_tests/sources/streams/http/test_availability_strategy.py`:
- Around line 108-110: The test currently asserts only the mapped message;
update the assertion after calling
HttpAvailabilityStrategy().check_availability(http_stream, logger) to also
verify the formatted context produced by _format_error_message — specifically
assert that reason includes the "Stream <stream.name> reported HTTP 404." (or
the literal "Stream" prefix plus "HTTP 404.") alongside "Requested resource not
found on source's API."; locate the test using is_available, reason,
http_stream, logger and add the extra assertion checking the new stream/status
prefix to harden the expectation.

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7f41401 and cd9aa33.

📒 Files selected for processing (12)
  • airbyte_cdk/sources/streams/http/error_handlers/default_error_mapping.py
  • airbyte_cdk/sources/streams/http/http_client.py
  • airbyte_cdk/utils/traced_exception.py
  • unit_tests/sources/declarative/checks/test_check_dynamic_stream.py
  • unit_tests/sources/declarative/checks/test_check_stream.py
  • unit_tests/sources/declarative/requesters/error_handlers/test_default_error_handler.py
  • unit_tests/sources/declarative/requesters/error_handlers/test_http_response_filter.py
  • unit_tests/sources/streams/http/error_handlers/test_http_status_error_handler.py
  • unit_tests/sources/streams/http/test_availability_strategy.py
  • unit_tests/sources/streams/http/test_http.py
  • unit_tests/sources/test_abstract_source.py
  • unit_tests/utils/test_traced_exception.py

@github-actions
Copy link

PyTest Results (Fast)

3 114 tests   - 755   3 101 ✅  - 756   6m 27s ⏱️ -24s
    1 suites ±  0      12 💤 ±  0 
    1 files   ±  0       1 ❌ +  1 

For more details on these failures, see this check.

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

This pull request removes 757 and adds 2 tests. Note that renamed tests count towards both.
unit_tests.sources.declarative.requesters.error_handlers.test_default_error_handler ‑ test_default_error_handler_with_custom_response_filter[_with_http_response_status_400_fail_with_default_failure_type-400-test_response_filter0-ResponseAction.RETRY-FailureType.system_error-HTTP Status Code: 400. Error: Bad request. Please check your request parameters.]
unit_tests.sources.declarative.requesters.error_handlers.test_default_error_handler ‑ test_default_error_handler_with_custom_response_filter[_with_http_response_status_403_fail_with_default_failure_type-403-test_response_filter2-ResponseAction.FAIL-FailureType.config_error-HTTP Status Code: 403. Error: Forbidden. You don't have permission to access this resource.]
unit_tests.sources.streams.http.error_handlers.test_default_backoff_strategy ‑ test_given_no_arguments_default_backoff_strategy_returns_default_values
unit_tests.sources.streams.http.error_handlers.test_default_backoff_strategy ‑ test_given_valid_arguments_default_backoff_strategy_returns_values
unit_tests.sources.streams.http.error_handlers.test_http_status_error_handler ‑ test_given_error_code_in_response_http_status_error_handler_returns_expected_actions[403-ResponseAction.FAIL-FailureType.config_error-HTTP Status Code: 403. Error: Forbidden. You don't have permission to access this resource.]
unit_tests.sources.streams.http.error_handlers.test_http_status_error_handler ‑ test_given_error_code_in_response_http_status_error_handler_returns_expected_actions[404-ResponseAction.FAIL-FailureType.system_error-HTTP Status Code: 404. Error: Not found. The requested resource was not found on the server.]
unit_tests.sources.streams.http.error_handlers.test_http_status_error_handler ‑ test_given_injected_error_mapping_returns_expected_action
unit_tests.sources.streams.http.error_handlers.test_http_status_error_handler ‑ test_given_no_response_argument_returns_expected_action
unit_tests.sources.streams.http.error_handlers.test_http_status_error_handler ‑ test_given_ok_response_http_status_error_handler_returns_success_action
unit_tests.sources.streams.http.error_handlers.test_http_status_error_handler ‑ test_given_requests_exception_returns_retry_action_as_transient_error
…
unit_tests.sources.declarative.requesters.error_handlers.test_default_error_handler ‑ test_default_error_handler_with_custom_response_filter[_with_http_response_status_400_fail_with_default_failure_type-400-test_response_filter0-ResponseAction.RETRY-FailureType.system_error-Bad request response from source's API.]
unit_tests.sources.declarative.requesters.error_handlers.test_default_error_handler ‑ test_default_error_handler_with_custom_response_filter[_with_http_response_status_403_fail_with_default_failure_type-403-test_response_filter2-ResponseAction.FAIL-FailureType.config_error-Source's API denied access. Configured credentials have insufficient permissions.]

@github-actions
Copy link

PyTest Results (Full)

3 872 tests   3 853 ✅  10m 59s ⏱️
    1 suites     12 💤
    1 files        7 ❌

For more details on these failures, see this check.

Results for commit cd9aa33.

@pnilan
Copy link
Contributor Author

Closing for now -- don't feel just rewording errors is useful.

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