Skip to content

fix(mcp): run_cloud_sync 409s give agents no recovery path (disabled-connection guidance swallowed; already-running has no job ID) #1403

Open

Description

@devin-ai-integration

Requested by AJ Steers (Aaron ("AJ") Steers (@aaronsteers)), from a hosted Cloud MCP failure review (Datadog LLM Obs tool spans, customer orgs only, Oct 14–16 2026).

Problem

run_cloud_sync fails with HTTP 409 in two situations, and in neither case does the agent get what it needs to recover.

1. Disabled connection: our guidance is dropped (bug)

CloudConnection.run_sync() catches a 409 on an inactive connection and raises AirbyteLibInputError with "re-enable the connection" guidance (added for #1205). But format_user_facing_error() in airbyte/mcp/_error_handling.py looks for an SDKError in the cause chain first and returns only the Cloud problem text. The agent never sees our message or guidance.

Repro on release-candidate/v1 (30775e81). I built the same exception chain run_sync() raises and passed it to format_user_facing_error():

disabled connection -> "State conflict"

Expected: Connection '<id>' is disabled (status 'inactive'), so a sync cannot be started. Re-enable the connection first (... update_cloud_connection with enabled=True), then retry.

This affects every PyAirbyte-raised error that wraps an SDKError and adds its own message or guidance, not just this one.

2. Sync already running: no job ID and no next step (guidance)

Cloud returns 409 try-again-later with "A sync is already running for: <connection_id>". The agent sees that text, but:

  • it doesn't get the running job's ID;
  • nothing tells it to watch that job with get_cloud_sync_status instead of starting another sync, or to call cancel_cloud_sync first if it really wants a restart;
  • the span records the error as upstream_error / fault upstream, but it is an expected state.

The docstring is only """Run a sync job on Airbyte Cloud.""".

Evidence (hosted MCP, customer orgs)

  • Oct 14: one customer session restarting syncs after a billing reactivation hit case 1 three times, on two connections (rc.29). The agent's next intent was "Retry restarting the ... sync after a transient conflict". It then called run_cloud_sync again on the same connection and got the same 409. That is what you'd expect if all it saw was "State conflict".
  • Case 2 came up 3 times in Oct 4–16, in 3 different orgs. I saw no retry loops after it, but none of those agents got a job ID.

Proposed fix

  1. In format_user_facing_error(), when the outermost error is an AirbyteLibError with its own message or guidance, show that first and add the Cloud detail after it. Don't replace it. Add a unit test for the disabled-connection chain.
  2. In run_sync() / run_cloud_sync, on try-again-later, look up the running job (newest-first get_previous_sync_logs(limit=1)). Then either raise an AirbyteLibInputError that includes the job ID and URL, or return a non-error "already running" result with them. Guidance: get_cloud_sync_status(connection_id, job_id=<id>) to monitor it; cancel_cloud_sync first if a restart is really wanted; don't retry run_cloud_sync.
  3. Expand the run_cloud_sync docstring to cover both 409 cases. It should also say that after a wait=True timeout the sync keeps running, so the agent should check its status rather than start a new sync.

Written by Devin

Activity

  1. devin-ai-integration commented on Oct 17, 2026

    @devin-ai-integration
    ContributorAuthor

    Correction to fix 1 above: the current behavior is intentional. AGENTS.md ("Agent-facing Cloud Error Messages") says PyAirbyte keeps the Cloud detail/title text as it is. It only adds its own guidance for actor-not-ready and 403 errors, and poor Cloud wording should be fixed in the platform.

    Because of that rule, the disabled-connection guidance in CloudConnection.run_sync() (added for #1205) never reaches agents. They only see Cloud's text, "State conflict". So this isn't a simple formatting bug. There are two options:

    • A. Add a narrow exception to the rule: in cloud_error_guidance(), add guidance for a 409 state-conflict from the create-job route when the connection is inactive, the same way actor-not-ready guidance is handled. Then the existing run_sync() hint is either removed or relied on.
    • B. Fix it in the platform: return a specific problem from the public jobs API, e.g. "Connection is disabled", instead of the generic "State conflict".

    Fixes 2 and 3 (job ID plus next steps for try-again-later, and the docstring) still apply. Under the same rule, fix 2 would also need to be an allowed guidance case in cloud_error_guidance(), or a non-error "already running" result.

    Written by Devin

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions