GitHub
06/29/2026, 4:47 AMvalidate_response override to GraphQLStream so that errors reported in a GraphQL response body are no longer silently ignored. After the existing HTTP status-code checks run, the method inspects the response body for an errors key. If errors are present and data is null, it raises FatalAPIError with the GraphQL error messages. If errors are present but data is also returned (partial success), it logs a warning and allows the sync to continue so usable records are not discarded.
The validation is also defensive about malformed responses:
• A body that is not valid JSON raises FatalAPIError (chained from the original decode error) instead of propagating a raw JSONDecodeError.
• A body that is JSON but not an object raises FatalAPIError instead of being treated as valid.
• Nonstandard errors shapes (a non-list value, entries that are not objects, or objects without a message key) are stringified into the error message instead of being silently dropped.
Why was this PR needed?
GraphQL APIs return HTTP 200 even when the request fails, placing the actual error inside the response body under an errors key (for example {"errors": [{"message": "Your token has insufficient scope"}], "data": null}).
GraphQLStream inherited validate_response from RESTStream, which only inspects the HTTP status code. Because the status was 200, these errors passed validation, the stream extracted zero records, and the sync completed with no error or warning. The reporter in the linked issue hit exactly this: a token with insufficient scope produced silent data loss that was difficult to diagnose. This change makes the failure visible at the point it occurs.
Design choices:
• The override lives in GraphQLStream rather than RESTStream, because body-level errors semantics are specific to GraphQL and should not affect plain REST streams.
• Total failure (data is null) raises, while partial success (data present alongside errors) warns and continues, because the GraphQL spec allows responses that carry both errors and usable data. Taps that need stricter or retriable handling can override the method.
• Malformed bodies (non-JSON or non-object) raise rather than pass silently, in line with the review feedback given on #3668.
What are the relevant issue numbers?
Closes #1421
Screenshots / Recordings (if applicable)
Tests in tests/core/rest/test_graphql.py cover error-only, multi-error, partial-success, clean, empty-errors, nonstandard error shapes, non-JSON body, and non-object body responses:
$ uv run pytest tests/core/rest/test_graphql.py -q
........... [100%]
11 passed in 0.06s
The behavior was also verified end to end with a minimal tap. Against the live countries GraphQL API, a query requesting an invalid field now aborts the sync with FatalAPIError: GraphQL errors in response: Cannot query field "bogusField" on type "Country"., where previously the same sync completed "successfully" with zero records.
Does this PR meet the acceptance criteria?
• Tests added for new/changed behavior
• All tests passing
• Follows project style guide
• No breaking changes introduced
• Documentation updated (if applicable) - not needed; behavior is internal to response validation
Summary by Sourcery
Surface GraphQL body-level errors during response validation to avoid silent failures when HTTP status is 200.
Bug Fixes:
• Raise a FatalAPIError when a GraphQL response contains errors and no data, preventing silent data loss.
• Log a warning instead of failing when GraphQL responses include both errors and usable data, allowing partial results to be processed.
Tests:
• Add GraphQLStream response validation tests covering error-only, partial-success, and success scenarios, including logging behavior.
meltano/sdkGitHub
07/14/2026, 8:23 PM