{"record":{"id":"0b7dcbf40a233c50","repo":"apache/cassandra","slug":"can-not-finish-joining-ring-sequence-is-in-an-inc","errorCode":null,"errorMessage":"Can not finish joining ring, sequence is in an incorrect state. If no progress is made, cancel the join process for this node and retry","messagePattern":"Can not finish joining ring, sequence is in an incorrect state\\. If no progress is made, cancel the join process for this node and retry","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"src/java/org/apache/cassandra/service/StorageService.java","lineNumber":1100,"sourceCode":"     * temporary copy of the {@link MultiStepOperation} and manually execute its next step after verifying expected\n     * invariants. This causes the MID step to fully execute, which then moves the sequence persisted in\n     * {@link ClusterMetadata}'s in-progress sequences onto the FINISH step, and we can complete the operation in the\n     * normal way with {@link InProgressSequences#finishInProgressSequences(MultiStepOperation.SequenceKey)}\n     * */\n    private void exitWriteSurveyMode()\n    {\n        ClusterMetadata metadata = ClusterMetadata.current();\n        NodeId id = metadata.myNodeId();\n        MultiStepOperation<?> sequence = metadata.inProgressSequences.get(id);\n\n        // Double check the conditions we verified in readyToFinishJoiningRing\n        if (sequence.kind() != MultiStepOperation.Kind.JOIN && sequence.kind() != MultiStepOperation.Kind.REPLACE)\n            throw new IllegalStateException(\"Can not finish joining ring as join sequence has not been started\");\n\n        if ((sequence.kind() == MultiStepOperation.Kind.JOIN && sequence.nextStep() != Transformation.Kind.MID_JOIN)\n            || (sequence.kind() == MultiStepOperation.Kind.REPLACE && sequence.nextStep() != Transformation.Kind.MID_REPLACE))\n        {\n            throw new IllegalStateException(\"Can not finish joining ring, sequence is in an incorrect state. \" +\n                                            \"If no progress is made, cancel the join process for this node and retry\");\n        }\n\n        if (sequence.kind() == MultiStepOperation.Kind.REPLACE && sequence.nextStep() != Transformation.Kind.MID_REPLACE)\n            throw new IllegalStateException(\"Can not finish joining ring, sequence is in an incorrect state. \" +\n                                            \"If no progress is made, cancel the join process for this node and retry\");\n\n        // Create a temporary new copy of the sequence with the finishJoining flag set to true and with streaming\n        // disabled, then execute its next step (the MID_*). We do this because effectively we want to jump over the\n        // MID_JOIN/MID_REPLACE of the \"real\" sequence. Note, this does not replace the existing sequence in\n        // ClusterMetadata with the temporary copy, but an effect of executing the MID step of the copy is that it will\n        // update the persisted state of the sequence leaving it with only the FINISH_* step to complete.\n        Transformation.Kind next = sequence.nextStep();\n        boolean success = (sequence instanceof BootstrapAndJoin)\n                          ? ((BootstrapAndJoin)sequence).finishJoiningRing().executeNext().isContinuable()\n                          : ((BootstrapAndReplace)sequence).finishJoiningRing().executeNext().isContinuable();\n\n        if (!success)","sourceCodeStart":1082,"sourceCodeEnd":1118,"githubUrl":"https://github.com/apache/cassandra/blob/88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1/src/java/org/apache/cassandra/service/StorageService.java#L1082-L1118","documentation":"Thrown by StorageService.finishJoiningRing when a JOIN/REPLACE sequence exists but its nextStep() is not the expected MID_JOIN (for JOIN) or MID_REPLACE (for REPLACE). The sequence is mid-flight at an unexpected step, so jumping straight to finish the join would skip required state transitions. Cassandra refuses rather than corrupting cluster metadata.","triggerScenarios":"Calling finishJoiningRing while the in-progress JOIN sequence's nextStep() is anything other than Transformation.Kind.MID_JOIN (e.g. it is still streaming, or past MID_JOIN), or while a REPLACE sequence's nextStep() is not Kind.MID_REPLACE.","commonSituations":"Operator interrupts streaming then calls finishjoin too early; the join advanced past the MID step already; racing the automatic in-flight operation progress with a manual finishjoin; attempting to force-finish a replace whose MID_REPLACE step hasn't been reached.","solutions":["Restart the node so in-flight operations automatically attempt to complete the next step","Wait/monitor streaming until the sequence reaches the MID_JOIN/MID_REPLACE step, then call finishjoin again","If no progress is made, cancel the join process for this node (nodetool cancelhandoff / cancel bootstrap) and retry the join from scratch","Inspect ClusterMetadata (nodetool metadata) to see the actual current step before retrying"],"exampleFix":"// before: forcing finish regardless of step\nstorageService.finishJoiningRing();\n\n// after: check the next step first\nMultiStepOperation<?> seq = ClusterMetadata.current().inProgressSequences.get(id);\nboolean ready = (seq.kind() == MultiStepOperation.Kind.JOIN && seq.nextStep() == Transformation.Kind.MID_JOIN)\n             || (seq.kind() == MultiStepOperation.Kind.REPLACE && seq.nextStep() == Transformation.Kind.MID_REPLACE);\nif (ready)\n    storageService.finishJoiningRing();","handlingStrategy":"validation","validationCode":"MultiStepOperation<?> seq = ClusterMetadata.current().inProgressSequences.get(id);\nboolean atMidStep = seq != null &&\n    ((seq.kind() == MultiStepOperation.Kind.JOIN && seq.nextStep() == Transformation.Kind.MID_JOIN) ||\n     (seq.kind() == MultiStepOperation.Kind.REPLACE && seq.nextStep() == Transformation.Kind.MID_REPLACE));\nif (!atMidStep) throw new IllegalStateException(\"Sequence not at MID_JOIN/MID_REPLACE; let in-flight operations progress first\");","typeGuard":"static boolean isAtMidJoinStep(MultiStepOperation<?> seq) {\n    return (seq.kind() == MultiStepOperation.Kind.JOIN && seq.nextStep() == Transformation.Kind.MID_JOIN)\n        || (seq.kind() == MultiStepOperation.Kind.REPLACE && seq.nextStep() == Transformation.Kind.MID_REPLACE);\n}","tryCatchPattern":"try {\n    storageService.finishJoiningRing();\n} catch (IllegalStateException e) {\n    if (e.getMessage().contains(\"sequence is in an incorrect state\")) {\n        logger.warn(\"Join sequence not at MID step; restart node or wait for in-flight ops\", e);\n    } else throw e;\n}","preventionTips":["Poll the sequence's nextStep() and only finish when it equals MID_JOIN/MID_REPLACE","Prefer restarting the node and letting in-flight operations progress over manual finishjoin","If stuck, cancel and re-run the join rather than repeatedly force-finishing"],"tags":["bootstrap","cluster-join","illegal-state","cassandra"],"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"}