Repository navigation
fix(low-code): report why the API rejected the last window or page - #1203
Merged
Merged
Conversation
When request window splitting or page size reduction runs out, the error that ends the read now carries the error handler's message for the last rejection in its internal message, so a 400 size limit can be told apart from a 504 timeout. - `RequestWindowSplitRequiredException` and `PageSizeReductionRequiredException` keep `error_message`, and their message describes the rejection only. Their "not set up" advice no longer shows up in the traceback of a stream that is set up correctly; the NotSupported exceptions and the factory's config check still carry it. - The depth cap message no longer blames the stream's cursor. Its internal message names the last rejected window. - `PageSizeReducer.reduce` takes the handler message and adds it to the errors it raises once the page size cannot be reduced any further. Fixes #1197
👋 Greetings, Airbyte Team Member!Here are some helpful tips and reminders for your convenience. 💡 Show Tips and TricksTesting This CDK VersionYou 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@bazarnov/fix-split-exhaustion-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 bazarnov/fix-split-exhaustion-error-messagesPR Slash CommandsAirbyte Maintainers can execute the following slash commands on your PR:
|
Daryna Ishchenko (darynaishchenko)
approved these changes
Oct 6, 2026
Baz (bazarnov)
deleted the
bazarnov/fix-split-exhaustion-error-messages
branch
October 6, 2026 13:40
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
👉 TL;DR: When a sync keeps narrowing a request and the API still refuses it, the stream's error now records why the API refused in its internal message and stack trace, and no longer wrongly suggests a setup mistake or a cursor that does not shrink the window.
Specifically,
RequestWindowSplitRequiredExceptionandPageSizeReductionRequiredExceptionkeeperror_messageand describe only the rejection, and the exhaustion errors raised bySimpleRetrieverandPageSizeReducer.reduceadd it to theirinternal_message.Changes
RequestWindowSplitRequiredExceptionandPageSizeReductionRequiredExceptionstoreerror_message, and theirmessagedescribes the rejection only:The API rejected the current request window of stream <name> and requires a smaller one: <error_message>(page size:... a page of stream <name> and asked the connector for a smaller one: ...).message, so the advice showed up in thestack_traceof correctly configured streams: once per split level, and in any unrelated failure inside a split child.RequestWindowSplitNotSupportedException,PageSizeReductionNotSupportedExceptionand the factory's config-time check.SimpleRetrieverwindow split exhaustion:internal_messageadds; the API rejected it: <error_message>.messageis unchanged.internal_messageadds; the API still rejected <last window>: <error_message>, andmessagekeeps only its first sentence. The dropped sentence blamed the stream's cursor, but the built-in cursor also reaches the cap by halving correctly on long windows (a 30-day window withPT1Sgranularity gets there at about 42-minute windows).PageSizeReducer.reducetakes the caught exception'serror_messageand appends; the API rejected the page: <error_message>to theinternal_messageof its three exhaustion errors (max attempts, transient floor, page size configured at the floor).SimpleRetrievertests, the twoHttpClientexact-message asserts updated, and two end-to-end tests through a realHttpResponseFilterextended.Review Spotlight
Reviewers with limited time, please review first:
simple_retriever.py#L643-L680: both window split exhaustion errorsrequest_window_split_exception.py#L23-L48: the control-flow message without the advice, and whypage_size_reducer.py#L113-L169:reduce(error_message)and the first of the three errors it annotatesRisks
message, theinternal_messageof the five exhaustion errors, themessageof the two control-flow exceptions, and themessageof the closingDuring the sync, the following streams did not sync successfullyerror. Records, state, schemas,failure_typeand request counts are unchanged.failure_typeis deliberately untouched: exhaustion staystransient_erroras decided in the feat: add SPLIT_REQUEST_WINDOW action for adaptive request-window splitting #1171 review, and a permanent rejection can later be carried by the matched filter'sfailure_type, which is set per response rather than per stream.messageis user-facing only on a stream that never handles the signal (a retriever not built onSimpleRetriever, or a PythonHttpStream), which now gets the rejection instead of the setup advice. No connector relies on that path: the only connector code raising one of these exceptions issource-google-ads, inside aSimpleRetrieverthat handles it.error_messagenow reachesinternal_messageand every traceback level, so long interpolated text makes those longer. It also reaches the user-facingmessageof the closing error, which is built from each stream error'sinternal_messagefor every stream failure; the stream error's ownmessagestays free of API text. A filter withouterror_messagecontributes the default mapping's text for its status code (PayPal's getsHTTP Status Code: 400. Error: Bad request. Please check your request parameters.), or nothing when that status has no default entry (TikTok's 40067 predicate matches HTTP 200). GitHub's filter message ends with "Retrying the same page with a smaller page size.", which now also shows in its exhaustioninternal_messageand the closing error'smessage.master@fc800755withgit grep -l -E '<pattern>' origin/master -- 'airbyte-integrations/connectors/*/manifest.yaml', plus #87641 and #87630, merged after that commit and read at their merge commits:SPLIT_REQUEST_WINDOW|request_window_splitting: 4 manifests (source-amplitude,source-paypal-transaction,source-twilioon SDM7.32.0,source-tiktok-marketingon SDM7.33.0), plussource-google-ads(airbyte-cdk ^7.33.0), whosecomponents.pyraisesRequestWindowSplitRequiredException(error_message=str(error)); that text now reaches the exhaustioninternal_message.REDUCE_PAGE_SIZE|page_size_reduction: 1 manifest (source-github, SDM7.32.0).git grep -n -E '(raise|except).*(RequestWindowSplitRequiredException|PageSizeReductionRequiredException)' origin/master -- 'airbyte-integrations/connectors/*.py': 0 hits; google-ads' raise above landed afterfc800755.failure_type, the unchanged floor and page sizemessages, or their ownfailure_message. Their unit tests pass against this branch, run from agit archiveofmaster@fc800755: amplitude 21, paypal 22, twilio 38, and github 86 intest_manifest_graphql_streams.pyandtest_components.py, which hold its page size reduction tests (its other files flake or crash locally onmaintoo). TikTok and google-ads assertions were read, not run.Test plan
git checkout origin/main -- airbyte_cdk/sources/declarative/retrievers/page_size_reducer.py airbyte_cdk/sources/declarative/retrievers/simple_retriever.py airbyte_cdk/sources/streams/http/page_size_reduction_exception.py airbyte_cdk/sources/streams/http/request_window_split_exception.py, thenpython -m pytest <the tests below>>> 9 failed;git checkout HEAD -- <the same files>, same command >> 9 passed.unit_tests/sources/declarative/retrievers/test_simple_retriever.py::test_given_window_still_rejected_when_splitting_is_exhausted_then_report_the_api_rejection(floor, depth cap)unit_tests/sources/declarative/retrievers/test_simple_retriever.py::test_given_reductions_exhausted_when_read_records_then_report_the_api_rejection(max attempts, minimum page size reached, page size starting at the minimum)unit_tests/sources/streams/http/test_http_client.py: the two exactmessageassertsunit_tests/sources/declarative/test_concurrent_declarative_source.py::test_given_reductions_exhausted_when_read_then_emit_a_transient_errorand::test_given_split_window_still_rejected_when_read_then_partition_is_not_checkpointedpython -m pytest unit_tests/sources/declarative/retrievers unit_tests/sources/streams/http unit_tests/sources/streams/concurrent unit_tests/sources/declarative/incremental unit_tests/sources/declarative/requesters unit_tests/sources/declarative/parsers>> 1882 passed.python -m pytest unit_tests/sources/declarative/test_concurrent_declarative_source.py -k "not test_read_with_concurrent_and_synchronous_streams">> 86 passed. The 3 deselected tests crash the interpreter locally on macOS, onmaintoo (Cached parent stream with more than one partition corrupts the shared sqlite request cache under concurrent reads (no such table: responses,file is not a database,no more rows available) #1150).ruff format,ruff check(0.11.5) andmypy --config-file mypy.ini(1.14.1) on the changed files: clean.Fixes #1197