Skip to content

feat(cloud): name conflicting destination streams and point to table_prefix on connection create - #1406

Open
Aaron ("AJ") Steers (aaronsteers) wants to merge 7 commits into
devin/1792200236-run-sync-409-guidancefrom
devin/1792200415-conflicting-stream-guidance
Open

Aaron ("AJ") Steers (aaronsteers) wants to merge 7 commits into
devin/1792200236-run-sync-409-guidancefrom
devin/1792200415-conflicting-stream-guidance

Conversation

@aaronsteers

Copy link
Copy Markdown
Member

Overview

👉 TL;DR: When a new connection would write to tables another connection on the same destination already uses, the error now names the clashing tables and the connections that own them, and says to set a table prefix.

Specifically, api_util.create_connection adds guidance built from the Cloud connection-conflicting-destination-stream problem's data.streams, and the create_connection_on_cloud / deploy_connection docs name table_prefix as the fix.

Stacked on #1405:

Closes #1404

Changes

  • api_util.create_connection: on a 400 error:connection-conflicting-destination-stream, adds guidance listing the clashing `namespace.stream` (connection `id`, ...) entries (first 10, then "and N more") and telling the caller to set table_prefix. Malformed entries are skipped. If no entry is usable, the guidance names table_prefix without the list. Other 400s are unchanged.
  • cloud_errors.parse_problem_payload: the existing bounded JSON parsing, pulled out of parse_cloud_error so both callers share it. No behavior change.
  • Docs: the create_connection_on_cloud docstring, its table_prefix field description, and the CloudWorkspace.deploy_connection table_prefix arg now state the one-table-per-destination rule.

Review Spotlight

Reviewers with limited time, please review first:

Risks

Test plan

  • Parametrized tests: two conflicting streams (one with no namespace), 12 streams (capped), a malformed entry mixed with valid ones, all entries malformed, and a non-conflict 400. Plus the format_user_facing_error output.
  • ruff, docstring lint, pyrefly, and MCP arg-trace parity tests: 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/1792200415-conflicting-stream-guidance' pyairbyte --help

# Install PyAirbyte from this branch for development:
pip install 'git+https://github.com/airbytehq/PyAirbyte.git@devin/1792200415-conflicting-stream-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.

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

Malformed Unicode in conflict fields can produce guidance that fails UTF-8 encoding.

1 open issue
What changed in this PR

Improves Cloud connection-creation errors by identifying destination table conflicts and explaining how to resolve them with table_prefix.

Changes:

  • Adds capped conflict details with stream names and connection IDs.
  • Extracts shared bounded JSON parsing.
  • Updates parameter documentation and error-formatting tests.
File Description
tests/​unit_tests/​test_cloud_api_util.py Tests conflict guidance, malformed entries, and agent-facing output.
airbyte/​mcp/​cloud.py Documents table conflicts and table_prefix.
airbyte/​cloud/​workspaces.py Clarifies prefix usage for deployed connections.
airbyte/​_util/​cloud_errors.py Extracts shared problem-payload parsing.
airbyte/​_util/​api_util.py Adds conflict details and prefix guidance.

Review effort: Balanced

Comment thread airbyte/_util/api_util.py

stream_name = f"{namespace}.{name}" if namespace else name
rendered_ids = ", ".join(f"`{connection_id}`" for connection_id in connection_ids)
return f"`{stream_name}` (connection {rendered_ids})"

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.

Not changing this. data.streams comes from Airbyte Cloud's own validation response, which is built from stream names and connection UUIDs that Cloud has already stored. We have no evidence of lone surrogates turning up there, so a guard would be speculative. The detail/title pre-check exists because those are free-text fields that our general Cloud error path renders for every tool. If this ever happens, the error will surface loudly instead of being silently skipped.

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.

👍 On it. I'm reversing my earlier "not changing this". The guard is small and handles the nested fields the same way parse_problem_payload already handles detail/title. Each rendered entry gets a UTF-8 encode check, and an entry that fails is skipped. If no entries are left, the generic table_prefix guidance is used. I'll add a test. This ships with the conflict-guidance rework.

Written by Devin

@aaronsteers
Aaron ("AJ") Steers (aaronsteers) marked this pull request as ready for review October 17, 2026 01:43
@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

devin/1792200236-run... d9f652f devin/1792200415-con... aaf4b2b +/-
65% 80% +15%
View coverage of the 10 most impacted files.
File devin/1792200236-run... d9f652f devin/1792200415-con... aaf4b2b +/-
airbyte/_util/api_util.py 36% 68% +32%
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

devin/1792200236-run... d9f652f devin/1792200415-con... aaf4b2b +/-
71% 82% +11%
View coverage of the 10 most impacted files.
File devin/1792200236-run... d9f652f devin/1792200415-con... aaf4b2b +/-
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

devin/1792200236-run... devin/1792200415-con... aaf4b2b +/-
— 76% —

Line coverage data for the devin/1792200236-run... branch is not yet available.

View coverage of the 10 most covered files.
File devin/1792200236-run... devin/1792200415-con... aaf4b2b +/-
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/_util/api_util.py — 68% —
airbyte/shared/...ql_processor.py — 68% —

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


Updated October 17, 2026 01:51 UTC

@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 2 potential issues.

Devin Review

Comment thread airbyte/_util/api_util.py
if e.status_code == HTTPStatus.BAD_REQUEST:
raise AirbyteLibInputError(
message=sdk_error_message(e),
guidance=_conflicting_destination_stream_guidance(e),

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.

🟡 Conflict errors expose prohibited extra guidance

For a conflicting-stream 400, create_connection adds table names and connection IDs to agent-facing errors. The Cloud error policy permits added guidance only for actor-not-ready and 403 failures.

Learn more

Cloud problem responses supply the error text through detail, or title when detail is empty. Both AGENTS.md and CONTRIBUTING.md limit PyAirbyte-added instructions to actor-not-ready setup and 403 access errors. The new conflict guidance adds an additional category and displays fields from data.streams, so it violates that restriction even when the Cloud message is preserved.

Example: A 400 conflict response with detail: "Conflicting stream" and a users stream owned by connection-1 now produces an agent error that also names users, connection-1, and table_prefix; previously it returned only the Cloud detail.

Recommended fix: Remove _conflicting_destination_stream_guidance from the create error path under the current policy. If this new exception to the policy is intended, update the mandatory Cloud error rules before adding it.

Suggested change
guidance=_conflicting_destination_stream_guidance(e),
guidance=None,

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.

Agreed: as written, this breaks the "Agent-facing Cloud Error Messages" rule. I've proposed adding this case to cloud_error_guidance() and updating the rule docs to list it, and I'm waiting on the maintainer's decision before reworking it.

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: may this become a third allowed guidance case? Agreed that, as written, it breaks the rule. My proposal is to add a connection-conflicting-destination-stream case to cloud_error_guidance(), driven by data.streams, and list it in the rule in AGENTS.md and CONTRIBUTING.md. The alternative is to drop the added guidance and keep only the table_prefix docstring changes. This reply replaces my earlier one, which didn't follow the reply format.

Written by Devin

Comment thread airbyte/_util/api_util.py
Comment on lines +2016 to +2018
stream_name = f"{namespace}.{name}" if namespace else name
rendered_ids = ", ".join(f"`{connection_id}`" for connection_id in connection_ids)
return f"`{stream_name}` (connection {rendered_ids})"

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.

🟡 Malformed stream names break conflict errors

When conflict metadata contains an escaped lone surrogate, _format_conflicting_destination_stream includes it in guidance. parse_problem_payload validates only the top-level message, so UTF-8 encoding of that guidance fails.

Learn more

A Cloud error body can be valid UTF-8 JSON while containing an escaped lone surrogate inside data.streams. parse_problem_payload checks encoding of the raw body and the selected top-level message, but does not validate nested strings. Rendering one of those strings into guidance produces text that cannot be encoded to UTF-8 for an agent response.

Example: The body {"type":"error:connection-conflicting-destination-stream","detail":"Conflict","data":{"streams":[{"streamName":"users\\ud800","connectionIds":["c1"]}]}} parses successfully. The guidance contains users\ud800, and encoding it as UTF-8 raises UnicodeEncodeError instead of returning the conflict message.

Recommended fix: Validate streamName, streamNamespace, and every connectionIds entry with UTF-8 encoding before formatting; skip malformed entries or fall back to a generic message. Test escaped lone surrogates in each field.

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.

Same point as the Copilot thread on this function. data.streams comes from Cloud's own validation response, which is built from stored stream names and connection UUIDs, and we have no evidence of lone surrogates turning up there. Not adding a guard for now; this code is being reworked anyway (see the policy thread above).

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.

👍 On it. I'm reversing my earlier decline here too, with the same fix as the Copilot thread on this function: each rendered entry gets a UTF-8 encode check, an entry that fails is skipped, the generic table_prefix guidance is the fallback, and there's a test.

Written by Devin

@aaronsteers
Aaron ("AJ") Steers (aaronsteers) added this pull request to stack #1411 October 20, 2026 01:24

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