{"record":{"id":"c9021ad95532d18f","repo":"TechnitiumSoftware/DnsServer","slug":"failed-to-ask-secondary-node-to-leave-the-specifi-c9021a","errorCode":null,"errorMessage":"Failed to ask Secondary node to leave: the specified node is the Cluster Primary node and cannot be removed.","messagePattern":"Failed to ask Secondary node to leave: the specified node is the Cluster Primary node and cannot be removed\\.","errorType":"exception","errorClass":"DnsServerException","httpStatus":null,"severity":"error","filePath":"DnsServerCore/Cluster/ClusterManager.cs","lineNumber":756,"sourceCode":"            TriggerRecordUpdateForClusterCatalogMemberZones();\n\n            return secondaryNode;\n        }\n\n        public async Task<ClusterNode> AskSecondaryNodeToLeaveClusterAsync(int secondaryNodeId)\n        {\n            if (!ClusterInitialized)\n                throw new DnsServerException(\"Failed to ask Secondary node to leave: the Cluster is not initialized.\");\n\n            if (GetSelfNode().Type != ClusterNodeType.Primary)\n                throw new DnsServerException(\"Failed to ask Secondary node to leave: only a Primary node can ask a Secondary node to leave the Cluster.\");\n\n            //find existing secondary node\n            if (!_clusterNodes.TryGetValue(secondaryNodeId, out ClusterNode secondaryNode))\n                throw new DnsServerException(\"Failed to ask Secondary node to leave: the specified node does not exist in the Cluster.\");\n\n            if (secondaryNode.Type == ClusterNodeType.Primary)\n                throw new DnsServerException(\"Failed to ask Secondary node to leave: the specified node is the Cluster Primary node and cannot be removed.\");\n\n            //ask secondary node to leave the cluster\n            await secondaryNode.AskSecondaryNodeToLeaveClusterAsync();\n\n            return secondaryNode;\n        }\n\n        public ClusterNode DeleteSecondaryNode(int secondaryNodeId)\n        {\n            if (!ClusterInitialized)\n                throw new DnsServerException(\"Failed to delete Secondary node: the Cluster is not initialized.\");\n\n            if (GetSelfNode().Type != ClusterNodeType.Primary)\n                throw new DnsServerException(\"Failed to delete Secondary node: only a Primary node can delete a Secondary node from the Cluster.\");\n\n            //find existing secondary node\n            IReadOnlyDictionary<int, ClusterNode> existingClusterNodes = _clusterNodes;\n","sourceCodeStart":738,"sourceCodeEnd":774,"githubUrl":"https://github.com/TechnitiumSoftware/DnsServer/blob/d0484b6c1e7439cdc53d67d81e9c876cda2ad756/DnsServerCore/Cluster/ClusterManager.cs#L738-L774","documentation":"Thrown by AskSecondaryNodeToLeaveClusterAsync when the node resolved by secondaryNodeId has Type == ClusterNodeType.Primary. The Primary node cannot be asked to leave — it is the authoritative owner of the cluster and must be removed via DeleteCluster instead. This guard runs after the node-exists check.","triggerScenarios":"AskSecondaryNodeToLeaveClusterAsync is called with the Primary node's ID. The lookup at line 752 succeeds but the type check at line 755 (secondaryNode.Type == ClusterNodeType.Primary) fires.","commonSituations":"Client or UI passes the Primary node's ID by mistake; iterating over all node IDs and calling leave without filtering by type; confusion between leave (for Secondaries) and delete (for the whole cluster).","solutions":["Pass a Secondary node's ID — use the node list to find one with Type == ClusterNodeType.Secondary.","If you want to remove the Primary, use DeleteCluster instead.","Filter the target node by type before calling: ensure secondaryNode.Type != ClusterNodeType.Primary."],"exampleFix":"// before\nawait _clusterManager.AskSecondaryNodeToLeaveClusterAsync(primaryNodeId);\n// after — target a Secondary, not the Primary\nvar target = _clusterManager.ClusterNodes.First(n => n.Value.Type == ClusterNodeType.Secondary);\nawait _clusterManager.AskSecondaryNodeToLeaveClusterAsync(target.Key);","handlingStrategy":"validation","validationCode":"// Ensure the target is a Secondary, not the Primary\nvar target = _dnsWebService.ClusterManager.ClusterNodes\n    .FirstOrDefault(n => n.Key == nodeId);\nif (target.Value is null)\n    throw new InvalidOperationException(\"Node not found.\");\nif (target.Value.Type == ClusterNodeType.Primary)\n    throw new InvalidOperationException(\"Cannot ask the Primary to leave; use DeleteCluster instead.\");\nawait _dnsWebService.ClusterManager.AskSecondaryNodeToLeaveClusterAsync(nodeId);","typeGuard":"static bool IsSecondaryNode(ClusterManager cm, int nodeId)\n    => cm.ClusterNodes.TryGetValue(nodeId, out var node) && node.Type == ClusterNodeType.Secondary;","tryCatchPattern":"try\n{\n    await _clusterManager.AskSecondaryNodeToLeaveClusterAsync(nodeId);\n}\ncatch (DnsServerException ex) when (ex.Message.Contains(\"Cluster Primary node and cannot be removed\"))\n{\n    throw new InvalidOperationException(\"The target is the Primary node. Use DeleteCluster to remove the entire cluster.\", ex);\n}","preventionTips":["Filter the node list to Secondaries only when presenting leave options.","Validate the target node's Type is Secondary before calling.","Use DeleteCluster (not leave) when the intent is to remove the Primary."],"tags":["cluster","leave","primary-node","wrong-target"],"backgroundTag":null,"analyzedSha":"d0484b6c1e7439cdc53d67d81e9c876cda2ad756","analyzedAt":"2026-08-13T22:57:35.508Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}