{"record":{"id":"5660feec1cdb7116","repo":"langflow-ai/langflow","slug":"label-add-app-ids-and-remove-app-ids-must-not-ov","errorCode":null,"errorMessage":"{label} add_app_ids and remove_app_ids must not overlap: {overlap}","messagePattern":"(.+?) add_app_ids and remove_app_ids must not overlap: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":422,"severity":"error","filePath":"src/backend/base/langflow/api/v1/mappers/deployments/watsonx_orchestrate/payloads.py","lineNumber":230,"sourceCode":"def _validate_api_remove_not_raw(*, operations: list[Any], raw_app_ids: set[str], attr_name: str, label: str) -> None:\n    for operation in operations:\n        remove_app_ids = getattr(operation, attr_name, None)\n        if not remove_app_ids:\n            continue\n        invalid_raw = sorted(raw_app_ids.intersection(set(remove_app_ids)))\n        if invalid_raw:\n            msg = f\"{label} must not reference connections app_ids: {invalid_raw}\"\n            raise ValueError(msg)\n\n\ndef _validate_api_add_remove_overlap(*, operations: list[Any], label: str) -> None:\n    for operation in operations:\n        add_app_ids = set(getattr(operation, \"add_app_ids\", []) or [])\n        remove_app_ids = set(getattr(operation, \"remove_app_ids\", []) or [])\n        overlap = sorted(add_app_ids.intersection(remove_app_ids))\n        if overlap:\n            msg = f\"{label} add_app_ids and remove_app_ids must not overlap: {overlap}\"\n            raise ValueError(msg)\n\n\ndef _validate_api_remove_conflicts(\n    *,\n    remove_ids: list[Any],\n    upsert_operations: list[Any],\n    remove_label: str,\n    upsert_attr_name: str,\n) -> None:\n    normalized_remove_ids = {str(remove_id).strip() for remove_id in remove_ids if str(remove_id).strip()}\n    normalized_upsert_ids = {\n        str(getattr(operation, upsert_attr_name, \"\")).strip()\n        for operation in upsert_operations\n        if str(getattr(operation, upsert_attr_name, \"\")).strip()\n    }\n    conflicts = sorted(normalized_remove_ids.intersection(normalized_upsert_ids))\n    if conflicts:\n        msg = f\"{remove_label} cannot be combined with upsert for the same id: {conflicts}\"","sourceCodeStart":212,"sourceCodeEnd":248,"githubUrl":"https://github.com/langflow-ai/langflow/blob/976ec789d2886a86de109c044d089d68e96c9a35/src/backend/base/langflow/api/v1/mappers/deployments/watsonx_orchestrate/payloads.py#L212-L248","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Decide the final state per app_id: it goes in add_app_ids or remove_app_ids, never both.","Fix client diff logic: last-write-wins per id, then partition into add/remove sets.","Validate before send: assert set(add).isdisjoint(set(remove)) for every operation item."],"exampleFix":"// before\n{\"tool_id\": \"t1\", \"add_app_ids\": [\"a\", \"b\"], \"remove_app_ids\": [\"b\"]}\n// after\n{\"tool_id\": \"t1\", \"add_app_ids\": [\"a\", \"b\"], \"remove_app_ids\": []}","handlingStrategy":"validation","validationCode":"for item in operations:\n    add, remove = set(item.get(\"add_app_ids\", [])), set(item.get(\"remove_app_ids\", []))\n    assert add.isdisjoint(remove), f\"overlap: {add & remove}\"","typeGuard":"const addRemoveDisjoint = (item: {add_app_ids?: string[]; remove_app_ids?: string[]}): boolean => {\n  const add = new Set(item.add_app_ids ?? []);\n  return (item.remove_app_ids ?? []).every(id => !add.has(id));\n};","tryCatchPattern":null,"preventionTips":["Partition desired end-state per app_id into add OR remove, never both.","Run a disjointness assertion in the request builder.","Watch for UI toggle races that enqueue both an add and a remove for the same id."],"tags":["watsonx-orchestrate","pydantic","validation","http-422","overlap","app-ids"],"backgroundTag":null,"analyzedSha":"976ec789d2886a86de109c044d089d68e96c9a35","analyzedAt":"2026-08-14T18:23:12.227Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}