{"record":{"id":"f424a27dada92c26","repo":"aeron-io/aeron","slug":"recordingid-recordingid-state-transition-currentstate","errorCode":null,"errorMessage":"(recordingId=\" + recordingId + \") state transition \" + currentState + \" -> \" + targetState + \" is not allowed","messagePattern":"\\(recordingId=\" \\+ recordingId \\+ \"\\) state transition \" \\+ currentState \\+ \" -> \" \\+ targetState \\+ \" is not allowed","errorType":"exception","errorClass":"AeronException","httpStatus":null,"severity":"error","filePath":"aeron-archive/src/main/java/io/aeron/archive/ArchiveTool.java","lineNumber":1033,"sourceCode":"        final File archiveDir,\n        final long recordingId,\n        final RecordingState expectedState,\n        final RecordingState targetState)\n    {\n        try (Catalog catalog = openCatalogReadWrite(archiveDir, INSTANCE, MIN_CAPACITY, null, null))\n        {\n            final MutableBoolean found = new MutableBoolean(false);\n            catalog.forEach((recordingDescriptorOffset, he, hDecoder, descriptorEncoder, descriptorDecoder) ->\n            {\n                if (descriptorDecoder.recordingId() == recordingId)\n                {\n                    found.set(true);\n                    final RecordingState currentState = hDecoder.state();\n                    if (targetState != currentState)\n                    {\n                        if (expectedState != currentState)\n                        {\n                            throw new AeronException(\"(recordingId=\" + recordingId + \") state transition \" +\n                                currentState + \" -> \" + targetState + \" is not allowed\");\n                        }\n                        he.state(targetState);\n                        out.println(\"(recordingId=\" + recordingId + \") changed state to \" + targetState);\n                    }\n                }\n            });\n\n            if (!found.get())\n            {\n                throw new AeronException(\"no recording found with recordingId: \" + recordingId);\n            }\n        }\n    }\n\n    private static String validateChecksumClass(final String checksumClassName)\n    {\n        final String className = null == checksumClassName ? null : checksumClassName.trim();","sourceCodeStart":1015,"sourceCodeEnd":1051,"githubUrl":"https://github.com/aeron-io/aeron/blob/6d60124e15e35c11b49ba2e3c2c2858a09a18803/aeron-archive/src/main/java/io/aeron/archive/ArchiveTool.java#L1015-L1051","documentation":"changeRecordingState throws AeronException when the recording's current state in the Catalog does not match the expectedState required for the requested transition (e.g. marking an already-invalid recording valid). The transition current -> target is not permitted unless the recording is in expectedState. Called by markRecordingValid / markRecordingInvalid.","triggerScenarios":"Calling markRecordingValid on a recording whose state is not INVALID (expectedState for valid-marking), or markRecordingInvalid on a recording not in VALID state; attempting to re-mark a recording that was already flipped.","commonSituations":"Re-running an archival recovery script twice; concurrent tooling changed the state between check and apply; operator marks a recording valid that was already invalid-then-marked-valid.","solutions":["Check the recording's current state first (describeRecording) and only mark when it matches the expected source state.","Skip the operation if the recording is already in the target state (the tool only throws when states differ AND don't match expected).","Undo the prior state change or use the correct markRecordingValid/Invalid for the current state."],"exampleFix":"// before\nArchiveTool.markRecordingValid(out, archiveDir, id); // recording already VALID (currentState != INVALID expected) -> throws\n// after\nRecordingState current = currentStateOf(archiveDir, id);\nif (current == RecordingState.INVALID) {\n    ArchiveTool.markRecordingValid(out, archiveDir, id);\n}","handlingStrategy":"validation","validationCode":"RecordingState current = readRecordingState(archiveDir, recordingId);\nRecordingState expected = targetValid ? RecordingState.INVALID : RecordingState.VALID;\nif (current != expected && current != target) {\n    throw new IllegalStateException(\"unexpected recording state \" + current + \", expected \" + expected);\n}","typeGuard":null,"tryCatchPattern":"try {\n    ArchiveTool.markRecordingValid(out, archiveDir, recordingId);\n} catch (AeronException e) {\n    if (e.getMessage().contains(\"is not allowed\")) {\n        // state already changed (likely by a previous run); inspect and continue or abort\n    } else throw e;\n}","preventionTips":["Make markRecordingValid/Invalid operations idempotent by checking current state first.","Avoid running multiple recovery/marking scripts concurrently on the same archive.","Log state transitions so operators know the current state before marking.","Skip the call when the recording is already in the target state."],"tags":["aeron","archive","state-machine","invalid-transition"],"backgroundTag":"invalid-state-transition","analyzedSha":"6d60124e15e35c11b49ba2e3c2c2858a09a18803","analyzedAt":"2026-09-12T11:17:07.683Z","contentChangedAt":"2026-09-12T11:17:07.683Z","schemaVersion":2},"datasetVersion":"2026-09-19T12:17:13.211Z"}