{"record":{"id":"e24dd60c4760bc08","repo":"TechnitiumSoftware/DnsServer","slug":"failed-to-add-secondary-node-node-id-already-exis","errorCode":null,"errorMessage":"Failed to add Secondary node: node ID already exists in the Cluster. Please try again.","messagePattern":"Failed to add Secondary node: node ID already exists in the Cluster\\. Please try again\\.","errorType":"exception","errorClass":"DnsServerException","httpStatus":null,"severity":"error","filePath":"DnsServerCore/Cluster/ClusterManager.cs","lineNumber":713,"sourceCode":"\n            IReadOnlyDictionary<int, ClusterNode> existingClusterNodes = _clusterNodes;\n\n            //validate for duplicate names\n            foreach (KeyValuePair<int, ClusterNode> existingClusterNode in existingClusterNodes)\n            {\n                if (existingClusterNode.Value.Name.Equals(secondaryNodeUrl.Host, StringComparison.OrdinalIgnoreCase))\n                    throw new DnsServerException(\"Failed to add Secondary node: A node with the same DNS Server Domain Name already exists in the Cluster. Please try again after changing the Secondary node's DNS Server Domain Name.\");\n            }\n\n            //add secondary node to cluster nodes\n            ClusterNode secondaryNode = new ClusterNode(this, secondaryNodeId, secondaryNodeUrl, secondaryNodeIpAddresses, ClusterNodeType.Secondary, ClusterNodeState.Unknown);\n            Dictionary<int, ClusterNode> updatedClusterNodes = new Dictionary<int, ClusterNode>(existingClusterNodes.Count + 1);\n\n            foreach (KeyValuePair<int, ClusterNode> existingClusterNode in existingClusterNodes)\n                updatedClusterNodes[existingClusterNode.Value.Id] = existingClusterNode.Value;\n\n            if (!updatedClusterNodes.TryAdd(secondaryNode.Id, secondaryNode))\n                throw new DnsServerException(\"Failed to add Secondary node: node ID already exists in the Cluster. Please try again.\");\n\n            if (updatedClusterNodes.Count > 255)\n                throw new DnsServerException(\"Failed to add Secondary node: a maximum of 255 nodes are supported by the Cluster.\");\n\n            IReadOnlyDictionary<int, ClusterNode> originalValue = Interlocked.CompareExchange(ref _clusterNodes, updatedClusterNodes, existingClusterNodes);\n            if (!ReferenceEquals(originalValue, existingClusterNodes))\n                throw new DnsServerException(\"Failed to add Secondary node: please try again.\");\n\n            secondaryNode.InitializeHeartbeatTimer();\n\n            //update cluster zone and save zone file\n            FindExistingRecordTtlValues(out uint nsTtl, out uint aTtl); //find existing record TTL values\n            AddClusterPrimaryZoneRecordsFor(secondaryNode, nsTtl, aTtl, secondaryNodeCertificate);\n\n            //update cluster catalog zone ACLs and save zone file\n            UpdateClusterCatalogZoneOptions();\n\n            //save all changes","sourceCodeStart":695,"sourceCodeEnd":731,"githubUrl":"https://github.com/TechnitiumSoftware/DnsServer/blob/d0484b6c1e7439cdc53d67d81e9c876cda2ad756/DnsServerCore/Cluster/ClusterManager.cs#L695-L731","documentation":"Thrown by JoinCluster when Dictionary.TryAdd(secondaryNode.Id, secondaryNode) fails because the node ID already exists in the updatedClusterNodes dictionary. Node IDs must be unique; this is a defensive guard that catches ID collisions even though the earlier name-uniqueness loop passed.","triggerScenarios":"JoinCluster is called with a secondaryNodeId that collides with an existing node's Id. The name check (error 149) compares hostnames, but this check compares integer IDs — so a different hostname with a reused ID triggers here.","commonSituations":"The caller reuses a secondaryNodeId from a previously removed node without verifying it was fully cleaned up; ID allocation logic on the caller side produced a duplicate; manual ID assignment collision.","solutions":["Use a unique secondaryNodeId that does not match any existing node's Id.","Delete the conflicting node (by Id) first if it is stale, then retry.","If IDs are caller-allocated, generate them from a source guaranteed unique (e.g., RandomNumberGenerator.GetInt32) rather than hardcoding."],"exampleFix":"// before\n_clusterManager.JoinCluster(42, nodeUrl, ips, cert); // 42 already exists\n// after\n_clusterManager.JoinCluster(RandomNumberGenerator.GetInt32(int.MaxValue), nodeUrl, ips, cert);","handlingStrategy":"validation","validationCode":"// Ensure the node ID does not collide before joining\nif (_dnsWebService.ClusterManager.ClusterNodes.Any(n => n.Key == nodeId))\n    throw new InvalidOperationException($\"Node ID {nodeId} already exists; use a unique ID.\");\n_dnsWebService.ClusterManager.JoinCluster(nodeId, nodeUrl, nodeIps, cert);","typeGuard":"static bool IsNodeIdUnique(ClusterManager cm, int nodeId)\n    => !cm.ClusterNodes.Any(n => n.Key == nodeId);","tryCatchPattern":"try\n{\n    _clusterManager.JoinCluster(nodeId, nodeUrl, nodeIps, cert);\n}\ncatch (DnsServerException ex) when (ex.Message.Contains(\"node ID already exists\"))\n{\n    // allocate a fresh ID and retry\n    throw;\n}","preventionTips":["Generate node IDs with RandomNumberGenerator.GetInt32(int.MaxValue) to minimize collision probability.","Check ClusterNodes for an existing ID before calling JoinCluster.","Delete stale nodes by ID before reusing an ID."],"tags":["cluster","join","id-conflict","uniqueness"],"backgroundTag":null,"analyzedSha":"d0484b6c1e7439cdc53d67d81e9c876cda2ad756","analyzedAt":"2026-08-13T22:57:35.508Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}