Skip to content

fix(cloud): keep PyAirbyte guidance on run_cloud_sync 409s and report the running job ID - #1405

Open
Aaron ("AJ") Steers (aaronsteers) wants to merge 4 commits into
release-candidate/v1from
devin/1792200236-run-sync-409-guidance
Open

Aaron ("AJ") Steers (aaronsteers) wants to merge 4 commits into
release-candidate/v1from
devin/1792200236-run-sync-409-guidance

Conversation

@aaronsteers

Copy link
Copy Markdown
Member

Overview

👉 TL;DR: When Airbyte Cloud refuses to start a sync with a conflict error, agents now get PyAirbyte's explanation and next steps. Before, they only got Cloud's terse "State conflict" text and retried.

Specifically, format_user_facing_error / agent_error_text keep a local error's message + guidance ahead of the Cloud detail, and CloudConnection.run_sync() turns 409 try-again-later into AirbyteConnectionSyncActiveError carrying the running job's ID.

Closes #1403

Changes

  • airbyte/mcp/_error_handling.py: when the chain holds a Cloud problem and a local AirbyteLibError with guidance, the agent text becomes <local message> <local guidance> Airbyte Cloud response: <cloud detail>. The suffix is omitted when the Cloud detail is already in the local text. Local errors without guidance still show only the Cloud text (+ Cloud guidance), as before. Span trace messages are unchanged.
  • CloudConnection.run_sync(): on 409 try-again-later for an enabled connection, it looks up the newest sync job. If that job is still running, it raises AirbyteConnectionSyncActiveError with the job ID, the job URL, and next steps: monitor with get_sync_result / get_cloud_sync_status, cancel with cancel_sync / cancel_cloud_sync only if a restart is wanted, and don't retry. If no running job is found, the original error is re-raised.
  • AirbyteConnectionSyncActiveError: docstring widened from the HTTP 423 state-update case to "a sync is currently running"; added to MCP_TOOL_USER_FACING_ERRORS.
  • run_cloud_sync docstring: covers the disabled-connection 409, the already-running 409, and what to do after a wait=True timeout.

Review Spotlight

Reviewers with limited time, please review first:

Risks

  • This is a behavior change for any wrapped Cloud error whose local error carries guidance: agents now see the local text first. One refactor(mcp): relay Cloud problem detail instead of the fixed error manifest #1368 test case (nested carrier) changes its expected output for this reason.
  • The already-running path makes one extra API call (get_previous_sync_logs(limit=1)), and only after Cloud has already rejected the sync with try-again-later.

Test plan

  • tests/unit_tests/test_mcp_tool_errors.py: disabled-connection chain, duplicate-message suppression, active-sync formatting.
  • tests/unit_tests/test_cloud_connections.py: try-again-later with a running job, with no job, and the disabled connection still taking priority.
  • ruff check, ruff format --check, docstring lint, and pyrefly check: all pass.

Link to Devin session: https://app.devin.ai/sessions/1fd07730c0494565ba33fec3854b26d2
Open in Devin Desktop: https://app.devin.ai/desktop/session/1fd07730c0494565ba33fec3854b26d2?variant=devin
Requested by: Aaron ("AJ") Steers (@aaronsteers)

@devin-ai-integration

Copy link
Copy Markdown
Contributor

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

  • Disable automatic comment, CI, and merge conflict monitoring

@github-actions

Copy link
Copy Markdown

👋 Greetings, Airbyte Team Member!

Here are some helpful tips and reminders for your convenience.

💡 Show Tips and Tricks

Testing This PyAirbyte Version

You can test this version of PyAirbyte using the following:

# Run PyAirbyte CLI from this branch:
uvx --from 'git+https://github.com/airbytehq/PyAirbyte.git@devin/1792200236-run-sync-409-guidance' pyairbyte --help

# Install PyAirbyte from this branch for development:
pip install 'git+https://github.com/airbytehq/PyAirbyte.git@devin/1792200236-run-sync-409-guidance'

PR Slash Commands

Airbyte Maintainers can execute the following slash commands on your PR:

  • /fix-pr - Fixes most formatting and linting issues
  • /uv-lock - Updates uv.lock file
  • /test-pr - Runs tests with the updated PyAirbyte
  • /prerelease - Builds and publishes a prerelease version to PyPI
  • /evals - Runs the Cloud MCP evals that use the MCP tools this PR changed (/evals full=true runs the full suite)
📚 Show Repo Guidance

Helpful Resources

Community Support

Questions? Join the #pyairbyte channel in our Slack workspace.

📝 Edit this welcome message.

@github-code-quality

github-code-quality Bot commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Code coverage overview

Languages: Python

Python / code-coverage/pytest-no-creds

release-candidate/v1 d9f652f devin/1792200236-run... 064295f +/-
65% 79% +14%
View coverage of the 10 most impacted files.
File release-candidate/v1 d9f652f devin/1792200236-run... 064295f +/-
airbyte/_util/api_util.py 36% 67% +31%
airbyte/mcp/cloud.py 52% 85% +33%
airbyte/cloud/connectors.py 34% 79% +45%
airbyte/mcp/_datadog.py 0% 50% +50%
airbyte/mcp/_user_identity.py 0% 93% +93%
airbyte/mcp/_telemetry.py 0% 94% +94%
airbyte/_direct...ctors/models.py 0% 95% +95%
airbyte/mcp/_sso_auth.py 0% 96% +96%
airbyte/cloud/models.py 0% 97% +97%
airbyte/mcp/int...nc_status_ui.py 0% 97% +97%

Python / code-coverage/pytest

release-candidate/v1 d9f652f devin/1792200236-run... 064295f +/-
71% 82% +11%
View coverage of the 10 most impacted files.
File release-candidate/v1 d9f652f devin/1792200236-run... 064295f +/-
airbyte/cloud/connectors.py 51% 83% +32%
airbyte/mcp/cloud.py 52% 85% +33%
airbyte/mcp/_datadog.py 0% 50% +50%
airbyte/mcp/int..._registry_ui.py 0% 92% +92%
airbyte/mcp/_user_identity.py 0% 93% +93%
airbyte/mcp/_telemetry.py 0% 94% +94%
airbyte/_direct...ctors/models.py 0% 95% +95%
airbyte/mcp/_sso_auth.py 0% 96% +96%
airbyte/cloud/models.py 0% 97% +97%
airbyte/mcp/int...nc_status_ui.py 0% 97% +97%

Python / code-coverage/pytest-unit

release-candidate/v1 devin/1792200236-run... 064295f +/-
— 76% —

Line coverage data for the release-candidate/v1 branch is not yet available.

View coverage of the 10 most covered files.
File release-candidate/v1 devin/1792200236-run... 064295f +/-
airbyte/cloud/models.py — 97% —
airbyte/mcp/_sso_auth.py — 96% —
airbyte/_direct...ctors/models.py — 95% —
airbyte/cloud/client.py — 90% —
airbyte/exceptions.py — 90% —
airbyte/mcp/cloud.py — 85% —
airbyte/progress.py — 80% —
airbyte/cloud/connectors.py — 79% —
airbyte/shared/...ql_processor.py — 68% —
airbyte/_util/api_util.py — 67% —

ℹ️ Calculations represent line coverage. Results may change as calculations are updated.


Updated October 17, 2026 01:51 UTC

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Global formatting exceeds the clarified recovery scope, diagnostic failures can mask conflicts, and the exception’s structured job ID remains unset.

3 open issues
What changed in this PR

Improves Cloud sync-conflict recovery by preserving local guidance and identifying an already-running job.

Changes:

  • Adds running-job lookup and recovery instructions for duplicate sync requests.
  • Prioritizes local guidance in agent-facing Cloud errors.
  • Expands conflict tests and sync-tool documentation.
File Description
tests/​unit_tests/​test_mcp_tool_errors.py Tests guidance preservation and active-sync formatting.
tests/​unit_tests/​test_cloud_connections.py Tests running, completed, missing, and disabled-job scenarios.
airbyte/​mcp/​cloud.py Documents conflict recovery and timeout behavior.
airbyte/​mcp/​_error_handling.py Changes error formatting and registers active-sync errors.
airbyte/​exceptions.py Broadens the active-sync exception docstring.
airbyte/​cloud/​connections.py Detects running syncs and supplies recovery guidance.

Review effort: Balanced

Comment thread airbyte/cloud/connections.py

job_id = sync_result.job_id
return AirbyteConnectionSyncActiveError(
connection_id=self.connection_id,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Valid. If the exception path stays in the rework, I'll pass job_id=job_id and assert it in the test.

Written by Devin

Copy link
Copy Markdown
Contributor

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? I've proposed that run_cloud_sync return a normal "already running" result with the job ID. That would make this point moot. If the exception stays, I'll pass job_id=job_id and assert error.job_id in the test. This reply replaces my earlier one, which didn't follow the reply format.

Written by Devin

Comment thread airbyte/mcp/_error_handling.py
@aaronsteers
Aaron ("AJ") Steers (aaronsteers) added this pull request to stack #1411 October 20, 2026 01:24
@aaronsteers
Aaron ("AJ") Steers (aaronsteers) marked this pull request as ready for review October 20, 2026 01:27

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Devin Review found 3 potential issues.

Devin Review

Comment thread airbyte/mcp/_error_handling.py
Comment on lines +381 to +383
active_sync_error = self._already_running_sync_error(ex)
if active_sync_error is not None:
raise active_sync_error from ex

Copy link
Copy Markdown
Contributor

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_sync raises an error outside the AirbyteConnectionSyncError hierarchy. Existing callers catching sync errors no longer handle that conflict.

Learn more

Previously a 409 conflict from run_sync propagated as AirbyteConnectionSyncError. The new running-job branch replaces it with AirbyteConnectionSyncActiveError, whose superclass is AirbyteCloudConnectionError. That class does not inherit from AirbyteConnectionSyncError. 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 enter recover() on a running-sync 409. With a running job detected, the new active exception bypasses it.

Recommended fix: Preserve the AirbyteConnectionSyncError catch 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.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor

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: AirbyteConnectionSyncActiveError subclasses AirbyteCloudConnectionError, not AirbyteConnectionSyncError, so except AirbyteConnectionSyncError no longer catches a running-sync 409. I've proposed that run_cloud_sync return a normal "already running" result instead of raising, which removes the break. If the exception stays, I'll keep raising AirbyteConnectionSyncError and put the job ID in its message and context.

Written by Devin

Comment thread airbyte/cloud/connections.py

This branch has not been deployed

No deployments
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