{"record":{"id":"59dd0bdcee4e0ae1","repo":"langgenius/dify","slug":"data-source-is-not-disabled","errorCode":null,"errorMessage":"Data source is not disabled.","messagePattern":"Data source is not disabled\\.","errorType":"exception","errorClass":"ValueError","httpStatus":400,"severity":"warning","filePath":"api/controllers/console/datasets/data_source.py","lineNumber":212,"sourceCode":"    def patch(\n        self, session: Session, current_tenant_id: str, binding_id: UUID, action: Literal[\"enable\", \"disable\"]\n    ) -> tuple[dict[str, str], int]:\n        binding_id_str = str(binding_id)\n        data_source_binding = session.scalar(\n            select(DataSourceOauthBinding).where(\n                DataSourceOauthBinding.id == binding_id_str, DataSourceOauthBinding.tenant_id == current_tenant_id\n            )\n        )\n        if data_source_binding is None:\n            raise NotFound(\"Data source binding not found.\")\n        # enable binding\n        match action:\n            case \"enable\":\n                if data_source_binding.disabled:\n                    data_source_binding.disabled = False\n                    data_source_binding.updated_at = naive_utc_now()\n                else:\n                    raise ValueError(\"Data source is not disabled.\")\n            # disable binding\n            case \"disable\":\n                if not data_source_binding.disabled:\n                    data_source_binding.disabled = True\n                    data_source_binding.updated_at = naive_utc_now()\n                else:\n                    raise ValueError(\"Data source is disabled.\")\n        return {\"result\": \"success\"}, 200\n\n\n@console_ns.route(\"/notion/pre-import/pages\")\nclass DataSourceNotionListApi(Resource):\n    @setup_required\n    @login_required\n    @account_initialization_required\n    @console_ns.doc(params=query_params_from_model(DataSourceNotionListQuery))\n    @console_ns.response(200, \"Success\", console_ns.models[NotionIntegrateInfoListResponse.__name__])\n    @with_current_user","sourceCodeStart":194,"sourceCodeEnd":230,"githubUrl":"https://github.com/langgenius/dify/blob/ef8544b173fd6cd7a8e71df2cab576e52bebbfbc/api/controllers/console/datasets/data_source.py#L194-L230","documentation":"Python ValueError raised at data_source.py:212 in the 'enable' branch of DataSourceApi.patch when the caller sends action=enable but data_source_binding.disabled is already False (binding is already enabled). This is an idempotency/contract violation: enabling an already-enabled binding is rejected. Note this is a bare ValueError (HTTP 500 unless a global handler maps ValueError), not a clean HTTP exception.","triggerScenarios":"PATCH /console/api/data-source/integrates/<binding_id>/enable on a binding whose disabled flag is already False. The else-branch of `if data_source_binding.disabled` fires.","commonSituations":"Double-click on the 'Enable' button; UI state out of sync with server state (shows disabled while server has it enabled); or a retry of a request that already succeeded.","solutions":["Refresh the integrations list to see the true disabled state before toggling.","Make the client idempotent: only send 'enable' when the binding is currently disabled.","Guard the endpoint: treat 'enable on enabled' as a no-op success instead of raising ValueError, if backward compatibility allows.","Replace the bare ValueError with a BadRequest/Conflict HTTP exception so the client gets a proper status code."],"exampleFix":"// before (data_source.py:206-212)\ncase \"enable\":\n    if data_source_binding.disabled:\n        data_source_binding.disabled = False\n        data_source_binding.updated_at = naive_utc_now()\n    else:\n        raise ValueError(\"Data source is not disabled.\")\n// after — idempotent enable\ncase \"enable\":\n    if data_source_binding.disabled:\n        data_source_binding.disabled = False\n        data_source_binding.updated_at = naive_utc_now()\n    # already enabled: no-op (still returns success)","handlingStrategy":"validation","validationCode":"// Only send 'enable' when the binding is currently disabled.\nif (action === 'enable' && binding.disabled === false) {\n  // already enabled — skip the call\n  return;\n}\nawait patchBinding(binding.id, 'enable');","typeGuard":"function shouldEnable(b: {disabled: boolean}): boolean { return b.disabled === true; }","tryCatchPattern":"try {\n  await patchBinding(bindingId, 'enable');\n} catch (e) {\n  if (/not disabled/i.test(String(e.message||e))) { /* already enabled, ignore */ return; }\n  throw e;\n}","preventionTips":["Read the binding's disabled flag from the latest list before toggling.","Make enable/disable actions idempotent in the UI (no double submits).","Push the team to convert the ValueError to a proper HTTP status."],"tags":["datasets","data-source","idempotency","state-conflict"],"backgroundTag":null,"analyzedSha":"ef8544b173fd6cd7a8e71df2cab576e52bebbfbc","analyzedAt":"2026-08-12T05:15:17.394Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}