{"record":{"id":"74a095611364756a","repo":"TechnitiumSoftware/DnsServer","slug":"failed-to-leave-cluster-only-secondary-nodes-can","errorCode":null,"errorMessage":"Failed to leave Cluster: only Secondary nodes can leave the Cluster.","messagePattern":"Failed to leave Cluster: only Secondary nodes can leave the Cluster\\.","errorType":"exception","errorClass":"DnsServerException","httpStatus":null,"severity":"error","filePath":"DnsServerCore/Cluster/ClusterManager.cs","lineNumber":1498,"sourceCode":"                catch\n                { }\n            }\n        }\n\n        public async Task LeaveClusterAsync(bool forceLeave)\n        {\n            if (!ClusterInitialized)\n                throw new DnsServerException(\"Failed to leave Cluster: the Cluster is not initialized.\");\n\n            ClusterNode primaryNode = GetPrimaryNode();\n\n            if (primaryNode.State == ClusterNodeState.Self)\n                throw new DnsServerException(\"Failed to leave Cluster: a Primary self node cannot leave the Cluster.\");\n\n            ClusterNode secondaryNode = GetSelfNode();\n\n            if (secondaryNode.Type != ClusterNodeType.Secondary)\n                throw new DnsServerException(\"Failed to leave Cluster: only Secondary nodes can leave the Cluster.\");\n\n            if (!forceLeave)\n            {\n                //delete self node from cluster on primary node\n                await primaryNode.DeleteSecondaryNodeAsync(secondaryNode);\n            }\n\n            //delete all cluster config\n            DeleteAllClusterConfig();\n        }\n\n        public async Task<ClusterNode> UpdatePrimaryNodeAsync(Uri primaryNodeUrl, IReadOnlyList<IPAddress> primaryNodeIpAddresses = null, int primaryNodeId = -1, CancellationToken cancellationToken = default)\n        {\n            if (!ClusterInitialized)\n                throw new DnsServerException(\"Failed to update Primary node: the Cluster is not initialized.\");\n\n            if (primaryNodeIpAddresses is null)\n            {","sourceCodeStart":1480,"sourceCodeEnd":1516,"githubUrl":"https://github.com/TechnitiumSoftware/DnsServer/blob/d0484b6c1e7439cdc53d67d81e9c876cda2ad756/DnsServerCore/Cluster/ClusterManager.cs#L1480-L1516","documentation":"Thrown by LeaveClusterAsync when GetSelfNode().Type is not ClusterNodeType.Secondary. Only Secondary nodes are permitted to leave a cluster; a Primary node must be demoted or transferred (see error 182) and Unknown-typed nodes are not valid leavers. The check runs after the primary-self check, so it typically catches a node whose type is Unknown or was already promoted.","triggerScenarios":"Calling LeaveClusterAsync on a node whose self type is Primary (and not detected as Self-primary by the earlier check) or Unknown — e.g. the node was just promoted to primary in-flight, or its type metadata is corrupt/uninitialized.","commonSituations":"State transition race where the node was promoted to Primary after the ClusterInitialized check but before the type check; a half-applied config that left Type as Unknown; operator confusion about which node they are acting on.","solutions":["Confirm this is actually a Secondary node; if it was promoted to Primary, follow the primary-removal path instead.","Refresh cluster state (TriggerResyncForConfig / re-login to primary) so node type is current, then retry leave.","As a last resort, force-leave (forceLeave=true) only if you understand it skips primary deregistration — but still ensure the node type is Secondary first."],"exampleFix":"// before\nawait clusterManager.LeaveClusterAsync(force: false);\n\n// after\nvar self = clusterManager.GetSelfNode();\nif (self.Type != ClusterNodeType.Secondary)\n    throw new InvalidOperationException($\"Cannot leave: this node is {self.Type}.\");\nawait clusterManager.LeaveClusterAsync(force: false);","handlingStrategy":"validation","validationCode":"var self = clusterManager.GetSelfNode();\nif (self.Type != ClusterNodeType.Secondary)\n    throw new InvalidOperationException($\"Node is {self.Type}; only secondaries can leave.\");\nawait clusterManager.LeaveClusterAsync(force: false);","typeGuard":"static bool IsSecondary(ClusterManager cm)\n    => cm.ClusterInitialized && cm.GetSelfNode().Type == ClusterNodeType.Secondary;","tryCatchPattern":"try { await clusterManager.LeaveClusterAsync(force: false); }\ncatch (DnsServerException ex) when (ex.Message.Contains(\"only Secondary nodes can leave\"))\n{ /* node is primary or unknown; follow demote/transfer path */ }","preventionTips":["Check GetSelfNode().Type before offering 'leave' in the UI.","Resync config if node type shows Unknown to refresh metadata.","Avoid re-running leave after a promotion that changed this node's role."],"tags":["cluster","role-guard","secondary-node","leave","technitium"],"backgroundTag":null,"analyzedSha":"d0484b6c1e7439cdc53d67d81e9c876cda2ad756","analyzedAt":"2026-08-13T22:57:35.508Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}