From ea0f38e08bf5943009090c264165912c5462b385 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 26 Mar 2026 14:10:46 +0000 Subject: [PATCH 1/3] fix(cdk): improve HTTP 400 default error message and reclassify as config_error - Change FailureType for HTTP 400 from system_error to config_error - Surface parsed API response body in user-facing error message when no custom error_message is set in the ErrorResolution - Keep verbose details in internal_message for debugging Resolves https://github.com/airbytehq/airbyte-internal-issues/issues/16112 Co-Authored-By: bot_apk --- .../error_handlers/default_error_mapping.py | 4 ++-- airbyte_cdk/sources/streams/http/http_client.py | 17 +++++++++++++++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/airbyte_cdk/sources/streams/http/error_handlers/default_error_mapping.py b/airbyte_cdk/sources/streams/http/error_handlers/default_error_mapping.py index 45716768f..910951c8d 100644 --- a/airbyte_cdk/sources/streams/http/error_handlers/default_error_mapping.py +++ b/airbyte_cdk/sources/streams/http/error_handlers/default_error_mapping.py @@ -30,8 +30,8 @@ ), 400: ErrorResolution( response_action=ResponseAction.FAIL, - failure_type=FailureType.system_error, - error_message="HTTP Status Code: 400. Error: Bad request. Please check your request parameters.", + failure_type=FailureType.config_error, + error_message=None, ), 401: ErrorResolution( response_action=ResponseAction.FAIL, diff --git a/airbyte_cdk/sources/streams/http/http_client.py b/airbyte_cdk/sources/streams/http/http_client.py index 3a0a62739..208f7814e 100644 --- a/airbyte_cdk/sources/streams/http/http_client.py +++ b/airbyte_cdk/sources/streams/http/http_client.py @@ -466,21 +466,34 @@ def _handle_error_resolution( if error_resolution.response_action == ResponseAction.FAIL: if response is not None: + parsed_api_error = self._error_message_parser.parse_response_error_message(response) filtered_response_message = filter_secrets( f"Request (body): '{str(request.body)}'. Response (body): '{self._get_response_body(response)}'. Response (headers): '{response.headers}'." ) - error_message = f"'{request.method}' request to '{request.url}' failed with status code '{response.status_code}' and error message: '{self._error_message_parser.parse_response_error_message(response)}'. {filtered_response_message}" + error_message = f"'{request.method}' request to '{request.url}' failed with status code '{response.status_code}' and error message: '{parsed_api_error}'. {filtered_response_message}" + + # Build user-facing message: prefer the error handler's custom message, + # fall back to a concise message with the parsed API error body + if error_resolution.error_message: + user_facing_message = error_resolution.error_message + elif parsed_api_error: + user_facing_message = filter_secrets( + f"API responded with HTTP {response.status_code}: {parsed_api_error}" + ) + else: + user_facing_message = f"API responded with HTTP {response.status_code}." else: error_message = ( f"'{request.method}' request to '{request.url}' failed with exception: '{exc}'" ) + user_facing_message = error_resolution.error_message or error_message # ensure the exception message is emitted before raised self._logger.error(error_message) raise AirbyteTracedException( internal_message=error_message, - message=error_resolution.error_message or error_message, + message=user_facing_message, failure_type=error_resolution.failure_type, ) From a6944a0dc61f0f18fa86d9af2d260f11bfb5b06d Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 26 Mar 2026 14:23:47 +0000 Subject: [PATCH 2/3] fix: update test expectations for HTTP 400 FailureType and error_message changes Co-Authored-By: bot_apk --- .../requesters/error_handlers/test_default_error_handler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/unit_tests/sources/declarative/requesters/error_handlers/test_default_error_handler.py b/unit_tests/sources/declarative/requesters/error_handlers/test_default_error_handler.py index bf4e1e321..0f5e73541 100644 --- a/unit_tests/sources/declarative/requesters/error_handlers/test_default_error_handler.py +++ b/unit_tests/sources/declarative/requesters/error_handlers/test_default_error_handler.py @@ -91,8 +91,8 @@ def test_default_error_handler_with_default_response_filter( parameters={}, ), ResponseAction.RETRY, - FailureType.system_error, - "HTTP Status Code: 400. Error: Bad request. Please check your request parameters.", + FailureType.config_error, + None, ), ( "_with_http_response_status_402_fail_with_default_failure_type", From 4555bd579909714811bcb48ab3d65f82d943993c Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 26 Mar 2026 14:34:03 +0000 Subject: [PATCH 3/3] fix: update resumable full refresh test for HTTP 400 config_error reclassification Co-Authored-By: bot_apk --- .../sources/mock_server_tests/test_resumable_full_refresh.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/unit_tests/sources/mock_server_tests/test_resumable_full_refresh.py b/unit_tests/sources/mock_server_tests/test_resumable_full_refresh.py index 04e232083..2b881fcd6 100644 --- a/unit_tests/sources/mock_server_tests/test_resumable_full_refresh.py +++ b/unit_tests/sources/mock_server_tests/test_resumable_full_refresh.py @@ -370,6 +370,6 @@ def test_resumable_full_refresh_failure(self, http_mocker): page=2 ) - assert actual_messages.errors[0].trace.error.failure_type == FailureType.system_error + assert actual_messages.errors[0].trace.error.failure_type == FailureType.config_error assert actual_messages.errors[0].trace.error.stream_descriptor.name == "justice_songs" - assert "Bad request" in actual_messages.errors[0].trace.error.message + assert "400" in actual_messages.errors[0].trace.error.message