langflow-ai/langflow · error · ValueError

{label} add_app_ids and remove_app_ids must not overlap: {ov

Error message

{label} add_app_ids and remove_app_ids must not overlap: {overlap}

What it means

API payload validator: within each upsert_flows / upsert_tools operation, add_app_ids and remove_app_ids must be disjoint. An id appearing in both lists is contradictory (bind and unbind in one operation), so ValueError '{label} add_app_ids and remove_app_ids must not overlap' is raised and FastAPI returns 422.

Source

Thrown at src/backend/base/langflow/api/v1/mappers/deployments/watsonx_orchestrate/payloads.py:230

def _validate_api_remove_not_raw(*, operations: list[Any], raw_app_ids: set[str], attr_name: str, label: str) -> None:
    for operation in operations:
        remove_app_ids = getattr(operation, attr_name, None)
        if not remove_app_ids:
            continue
        invalid_raw = sorted(raw_app_ids.intersection(set(remove_app_ids)))
        if invalid_raw:
            msg = f"{label} must not reference connections app_ids: {invalid_raw}"
            raise ValueError(msg)


def _validate_api_add_remove_overlap(*, operations: list[Any], label: str) -> None:
    for operation in operations:
        add_app_ids = set(getattr(operation, "add_app_ids", []) or [])
        remove_app_ids = set(getattr(operation, "remove_app_ids", []) or [])
        overlap = sorted(add_app_ids.intersection(remove_app_ids))
        if overlap:
            msg = f"{label} add_app_ids and remove_app_ids must not overlap: {overlap}"
            raise ValueError(msg)


def _validate_api_remove_conflicts(
    *,
    remove_ids: list[Any],
    upsert_operations: list[Any],
    remove_label: str,
    upsert_attr_name: str,
) -> None:
    normalized_remove_ids = {str(remove_id).strip() for remove_id in remove_ids if str(remove_id).strip()}
    normalized_upsert_ids = {
        str(getattr(operation, upsert_attr_name, "")).strip()
        for operation in upsert_operations
        if str(getattr(operation, upsert_attr_name, "")).strip()
    }
    conflicts = sorted(normalized_remove_ids.intersection(normalized_upsert_ids))
    if conflicts:
        msg = f"{remove_label} cannot be combined with upsert for the same id: {conflicts}"

View on GitHub (pinned to 976ec789d2)

Solutions

  1. Decide the final state per app_id: it goes in add_app_ids or remove_app_ids, never both.
  2. Fix client diff logic: last-write-wins per id, then partition into add/remove sets.
  3. Validate before send: assert set(add).isdisjoint(set(remove)) for every operation item.

Example fix

// before
{"tool_id": "t1", "add_app_ids": ["a", "b"], "remove_app_ids": ["b"]}
// after
{"tool_id": "t1", "add_app_ids": ["a", "b"], "remove_app_ids": []}
Defensive patterns

Strategy: validation

Validate before calling

for item in operations:
    add, remove = set(item.get("add_app_ids", [])), set(item.get("remove_app_ids", []))
    assert add.isdisjoint(remove), f"overlap: {add & remove}"

Type guard

const addRemoveDisjoint = (item: {add_app_ids?: string[]; remove_app_ids?: string[]}): boolean => {
  const add = new Set(item.add_app_ids ?? []);
  return (item.remove_app_ids ?? []).every(id => !add.has(id));
};

Prevention

When it happens

Trigger: PATCH a wxO deployment with an upsert_flows or upsert_tools item whose add_app_ids and remove_app_ids arrays share at least one app_id.

Common situations: UI toggle race producing both add and remove for the same app; naive diff code that keeps an id in remove when the user re-adds it; merging two partial operation payloads into one item.

Related errors


AI-assisted analysis of langflow-ai/langflow@976ec789d2 (2026-08-14). Data as JSON: /api/errors/5660feec1cdb7116. Report an issue: GitHub.