Repository navigation
fix(cloud): keep PyAirbyte guidance on run_cloud_sync 409s and report the running job ID
#1405
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
eb371d7
4cdcf7f
ea7d6d3
064295f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,7 @@ | |
|
|
||
| from airbyte._direct_connectors import api_util as agents_api_util | ||
| from airbyte._util import api_util | ||
| from airbyte._util.cloud_errors import parse_cloud_error | ||
| from airbyte.cloud._connection_catalog import ( | ||
| _denormalize_catalog_to_api, | ||
| _is_protocol_catalog_format, | ||
|
|
@@ -35,6 +36,7 @@ | |
| from airbyte.cloud.sync_results import SyncResult | ||
| from airbyte.exceptions import ( | ||
| AirbyteCloudError, | ||
| AirbyteConnectionSyncActiveError, | ||
| AirbyteConnectionSyncError, | ||
| AirbyteLibInputError, | ||
| AirbyteMissingResourceError, | ||
|
|
@@ -375,6 +377,10 @@ def run_sync( | |
| ), | ||
| context={"connection_id": self.connection_id}, | ||
| ) from ex | ||
| if ex.context and ex.context.get("status_code") == HTTPStatus.CONFLICT: | ||
| active_sync_error = self._already_running_sync_error(ex) | ||
| if active_sync_error is not None: | ||
| raise active_sync_error from ex | ||
| raise | ||
| sync_result = SyncResult( | ||
| workspace=self.workspace, | ||
|
|
@@ -391,6 +397,55 @@ def run_sync( | |
|
|
||
| return sync_result | ||
|
|
||
| def _already_running_sync_error( | ||
| self, | ||
| error: AirbyteConnectionSyncError, | ||
| ) -> AirbyteConnectionSyncActiveError | None: | ||
| """Return actionable guidance when Cloud rejects a duplicate running sync.""" | ||
| sdk_error = error.__cause__ | ||
| if not isinstance(sdk_error, api_util.SDKError): | ||
| return None | ||
|
|
||
| problem = parse_cloud_error( | ||
| sdk_error.status_code, | ||
| api_util.error_response_body(sdk_error), | ||
| ) | ||
| if problem.slug != "try-again-later": | ||
| return None | ||
|
|
||
| sync_results = self.get_previous_sync_logs( | ||
| limit=1, | ||
| job_type=JobTypeEnum.SYNC, | ||
| ) | ||
|
aaronsteers marked this conversation as resolved.
|
||
| sync_result = sync_results[0] if sync_results else None | ||
| if sync_result is None or sync_result.is_job_complete(): | ||
| return None | ||
|
aaronsteers marked this conversation as resolved.
|
||
|
|
||
| job_id = sync_result.job_id | ||
| return AirbyteConnectionSyncActiveError( | ||
| connection_id=self.connection_id, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Valid. If the exception path stays in the rework, I'll pass
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🙋 Human Input Needed: should the already-running case still raise an exception? I've proposed that |
||
| message=( | ||
| f"A sync is already running for connection '{self.connection_id}' " | ||
| f"(job {job_id})." | ||
| ), | ||
| guidance=( | ||
| "Don't start another sync. Monitor the running job with " | ||
| f"`connection.get_sync_result(job_id={job_id})` or the " | ||
| "`get_cloud_sync_status` MCP tool " | ||
| f"(`connection_id='{self.connection_id}', job_id={job_id}`). " | ||
| "Only if the user wants to restart it, cancel it first with " | ||
| f"`connection.cancel_sync(job_id={job_id})` or the " | ||
| "`cancel_cloud_sync` MCP tool " | ||
| f"(`connection_id='{self.connection_id}', job_id={job_id}`), " | ||
| f"then run the sync again. Job URL: {sync_result.job_url}." | ||
| ), | ||
| context={ | ||
| "connection_id": self.connection_id, | ||
| "job_id": job_id, | ||
| "job_url": sync_result.job_url, | ||
| }, | ||
| ) | ||
|
|
||
| def _get_latest_cancellable_sync_job_id(self) -> int: | ||
| """Get the latest cancellable sync job ID.""" | ||
| sync_results = self.get_previous_sync_logs( | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 Running conflicts bypass sync error handlers
When a sync is already running,
run_syncraises an error outside theAirbyteConnectionSyncErrorhierarchy. Existing callers catching sync errors no longer handle that conflict.Learn more
Previously a 409 conflict from
run_syncpropagated asAirbyteConnectionSyncError. The new running-job branch replaces it withAirbyteConnectionSyncActiveError, whose superclass is AirbyteCloudConnectionError. That class does not inherit fromAirbyteConnectionSyncError. Code that catches sync errors to handle failed starts now misses the active-sync case.Example:
try: connection.run_sync(); except AirbyteConnectionSyncError: recover()used to enterrecover()on a running-sync 409. With a running job detected, the new active exception bypasses it.Recommended fix: Preserve the
AirbyteConnectionSyncErrorcatch contract for failed sync starts, either by adjusting exception inheritance with consideration for the pre-existing HTTP 423 state-update use, or by providing a compatible exception type for this path.Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🙋 Human Input Needed: should the already-running case still raise an exception? The break is real:
AirbyteConnectionSyncActiveErrorsubclassesAirbyteCloudConnectionError, notAirbyteConnectionSyncError, soexcept AirbyteConnectionSyncErrorno longer catches a running-sync 409. I've proposed thatrun_cloud_syncreturn a normal "already running" result instead of raising, which removes the break. If the exception stays, I'll keep raisingAirbyteConnectionSyncErrorand put the job ID in its message andcontext.Written by Devin