{"record":{"id":"6d39a276443ce44a","repo":"apache/cassandra","slug":"node-nodeiporid-is-already-in-joined-state","errorCode":null,"errorMessage":"Node ${nodeIpOrId} is already in JOINED state.","messagePattern":"Node (.+?) is already in JOINED state\\.","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"src/java/org/apache/cassandra/tools/CMSOfflineTool.java","lineNumber":653,"sourceCode":"        @CommandLine.ArgGroup(exclusive = true, multiplicity = \"1\")\n        NodeIdentifierOption nodeIdentifierOption;\n        @Option(names = { \"-o\", \"--output-file\" },\n        description = \"Output file path for storing the updated Cluster Metadata.\")\n        private String outputFilePath;\n\n        @Override\n        protected void execute(Output output) throws IOException\n        {\n            ClusterMetadata metadata = parseClusterMetadata();\n            NodeId nodeId = nodeIdentifierOption.getNodeId(metadata);\n            Set<Token> tokenSet = new HashSet<>(tokens.size());\n            Token.TokenFactory tokenFactory = metadata.partitioner.getTokenFactory();\n            tokens.forEach(t -> tokenSet.add(tokenFactory.fromString(t)));\n\n            NodeState nodeState = metadata.directory.peerState(nodeId);\n            if (nodeState == NodeState.JOINED)\n            {\n                throw new IllegalArgumentException(\"Node \" + nodeIdentifierOption.getNodeIpOrId() +\n                                                   \" is already in JOINED state.\");\n            }\n\n            ClusterMetadata updatedMetadata;\n            if (metadata.inProgressSequences.get(nodeId) != null)\n            {\n                MultiStepOperation<?> multiStepOperation = metadata.inProgressSequences.get(nodeId);\n                if (multiStepOperation.kind() != MultiStepOperation.Kind.JOIN)\n                {\n                    throw new IllegalArgumentException(\"Another sequence of kind \" + multiStepOperation.kind() +\n                                                       \" is in progress for node \" + nodeIdentifierOption.getNodeIpOrId() +\n                                                       \". Cannot proceed with force join.\");\n                }\n                BootstrapAndJoin bootstrapAndJoin = (BootstrapAndJoin) multiStepOperation;\n                Set<Token> sequenceTokens = bootstrapAndJoin.finishJoin.tokens;\n                if (tokens.isEmpty()\n                    || (tokenSet.size() == sequenceTokens.size() && sequenceTokens.containsAll(tokenSet)))\n                {","sourceCodeStart":635,"sourceCodeEnd":671,"githubUrl":"https://github.com/apache/cassandra/blob/88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1/src/java/org/apache/cassandra/tools/CMSOfflineTool.java#L635-L671","documentation":"Thrown by the ForceJoin transformation in CMSOfflineTool when metadata.directory.peerState(nodeId) reports NodeState.JOINED for the target node. A force join only makes sense for nodes not fully joined, so re-joining an already-joined node is rejected as an invalid state transition.","triggerScenarios":"Running the offline force-join subcommand against a node whose peer state in the cluster metadata is already JOINED.","commonSituations":"Re-running the tool after a previous force join succeeded; assuming a node is un-joined because it is down; operating on the wrong node id.","solutions":["Check the node's state in the metadata first; if JOINED, no action is needed.","Target a different node id if you intended another host.","Use the appropriate offline operation for an already-joined node (e.g. restart, or cleanup) instead of force join."],"exampleFix":"// before\n// forceJoin(metadata, idOfJoinedNode, tokens)\n// after\n// if (metadata.directory.peerState(nodeId) != NodeState.JOINED) forceJoin(...);","handlingStrategy":"validation","validationCode":"if (metadata.directory.peerState(nodeId) == NodeState.JOINED)\n    return; // nothing to force-join","typeGuard":null,"tryCatchPattern":"try { forceJoin(metadata, nodeId, tokens); } catch (IllegalArgumentException e) { logger.info(\"Skipping: \" + e.getMessage()); }","preventionTips":["Inspect peerState before force join","Make tool runs idempotent by checking state first","Track which nodes were already force-joined"],"tags":["cluster-metadata","node-state","invalid-state-transition"],"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"}