{"record":{"id":"fd1ce8793eeffccd","repo":"apache/cassandra","slug":"initial-cms-node-needs-to-be-fully-joined-not-s","errorCode":null,"errorMessage":"Initial CMS node needs to be fully joined, not: %s","messagePattern":"Initial CMS node needs to be fully joined, not: (.+?)","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"src/java/org/apache/cassandra/tcm/ClusterMetadataService.java","lineNumber":439,"sourceCode":"        return ClusterMetadata.current().isCMSMember(peer);\n    }\n\n    public void upgradeFromGossip(List<String> ignoredEndpoints)\n    {\n        Set<InetAddressAndPort> ignored = ignoredEndpoints.stream().map(InetAddressAndPort::getByNameUnchecked).collect(toSet());\n        if (ignored.contains(FBUtilities.getBroadcastAddressAndPort()))\n        {\n            String msg = String.format(\"Can't ignore local host %s when doing CMS migration\", FBUtilities.getBroadcastAddressAndPort());\n            logger.error(msg);\n            throw new IllegalStateException(msg);\n        }\n\n        ClusterMetadata metadata = metadata();\n        if (metadata.myNodeState() != NodeState.JOINED)\n        {\n            String msg = String.format(\"Initial CMS node needs to be fully joined, not: %s\", metadata.myNodeState());\n            logger.error(msg);\n            throw new IllegalStateException(msg);\n        }\n\n        Set<InetAddressAndPort> existingMembers = metadata.fullCMSMembers();\n\n        if (!metadata.directory.allAddresses().containsAll(ignored))\n        {\n            Set<InetAddressAndPort> allAddresses = Sets.newHashSet(metadata.directory.allAddresses());\n            String msg = String.format(\"Ignored host(s) %s don't exist in the cluster\", Sets.difference(ignored, allAddresses));\n            logger.error(msg);\n            throw new IllegalStateException(msg);\n        }\n\n        for (Map.Entry<NodeId, NodeVersion> entry : metadata.directory.versions.entrySet())\n        {\n            NodeVersion version = entry.getValue();\n            InetAddressAndPort ep = metadata.directory.getNodeAddresses(entry.getKey()).broadcastAddress;\n            if (ignored.contains(ep))\n            {","sourceCodeStart":421,"sourceCodeEnd":457,"githubUrl":"https://github.com/apache/cassandra/blob/88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1/src/java/org/apache/cassandra/tcm/ClusterMetadataService.java#L421-L457","documentation":"During a gossip-to-TCM upgrade, the node attempting to become the initial CMS (Cluster Metadata Service) must itself be fully JOINED in the current ClusterMetadata. If its node state is anything else (e.g. BOOTSTRAPPING, LEAVING, CANDIDATE), upgradeFromGossip logs the message and throws IllegalStateException because a non-joined node cannot safely seed the metadata cluster.","triggerScenarios":"Calling ClusterMetadataService.upgradeFromGossip() while metadata.myNodeState() != NodeState.JOINED — e.g. the initiating node is still bootstrapping, decommissioning/leaving, or has a partially completed state transition.","commonSituations":"Operator runs the CMS upgrade step on the wrong node (one still mid-bootstrap), a previous bootstrap/decommission was interrupted leaving stale node state, or the upgrade is triggered before the node has fully joined the ring during a rolling upgrade from pre-TCM versions.","solutions":["Pick a node whose NodeState is JOINED to run the upgrade from gossip (verify with nodetool status / ClusterMetadata.current().myNodeState()).","Complete or roll back any in-flight bootstrap/decommission operation on the initiating node before upgrading.","If node state is stuck, repair the node state (finish bootstrap with proper RF or remove/replace the node) and retry upgradeFromGossip.","Ensure the cluster upgrade procedure starts after ALL nodes have joined and the cluster is stable."],"exampleFix":"// before\nclusterMetadataService.upgradeFromGossip(ignoredSet); // called on a BOOTSTRAPPING node\n// after\nif (ClusterMetadata.current().myNodeState() == NodeState.JOINED)\n    clusterMetadataService.upgradeFromGossip(ignoredSet);\nelse\n    throw new IllegalStateException(\"Run upgradeFromGossip on a fully joined node\");","handlingStrategy":"validation","validationCode":"if (ClusterMetadata.current().myNodeState() != NodeState.JOINED) throw new IllegalStateException(\"Node not JOINED; cannot upgrade from gossip\");","typeGuard":"boolean canUpgradeFromGossip = ClusterMetadata.current().myNodeState() == NodeState.JOINED;","tryCatchPattern":null,"preventionTips":["Only run gossip-to-TCM upgrade from a fully joined, stable node","Complete bootstrap/decommission before initiating migration","Script the upgrade to assert node state first"],"tags":["upgrade","cluster-metadata","node-state","illegal-state"],"backgroundTag":"invalid-state-transition","analyzedSha":"88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1","analyzedAt":"2026-09-10T07:29:22.284Z","contentChangedAt":"2026-09-10T07:29:22.284Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}