aeron-io/aeron · error · AeronException
(recordingId=" + recordingId + ") state transition " +…
Error message
(recordingId=" + recordingId + ") state transition " + currentState + " -> " + targetState + " is not allowed
What it means
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.
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.
Example fix
// before
ArchiveTool.markRecordingValid(out, archiveDir, id); // recording already VALID (currentState != INVALID expected) -> throws
// after
RecordingState current = currentStateOf(archiveDir, id);
if (current == RecordingState.INVALID) {
ArchiveTool.markRecordingValid(out, archiveDir, id);
} Defensive patterns
Strategy: validation
Validate before calling
RecordingState current = readRecordingState(archiveDir, recordingId);
RecordingState expected = targetValid ? RecordingState.INVALID : RecordingState.VALID;
if (current != expected && current != target) {
throw new IllegalStateException("unexpected recording state " + current + ", expected " + expected);
} Try / catch
try {
ArchiveTool.markRecordingValid(out, archiveDir, recordingId);
} catch (AeronException e) {
if (e.getMessage().contains("is not allowed")) {
// state already changed (likely by a previous run); inspect and continue or abort
} else throw e;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Aeron client instance must set…
- Aeron client must use a RethrowingErrorHandler
- AeronArchive.Context.controlRequestChannel must be set
- AeronArchive.Context.controlResponseChannel must be set
- AeronArchive.Context.messageRetryAttempts must be > 0, got:
AI-assisted analysis of aeron-io/aeron@6d60124e15 (2026-09-12).
Data as JSON: /api/errors/f424a27dada92c26.
Report an issue: GitHub.
Appendix: source
Thrown at aeron-archive/src/main/java/io/aeron/archive/ArchiveTool.java:1033
final File archiveDir,
final long recordingId,
final RecordingState expectedState,
final RecordingState targetState)
{
try (Catalog catalog = openCatalogReadWrite(archiveDir, INSTANCE, MIN_CAPACITY, null, null))
{
final MutableBoolean found = new MutableBoolean(false);
catalog.forEach((recordingDescriptorOffset, he, hDecoder, descriptorEncoder, descriptorDecoder) ->
{
if (descriptorDecoder.recordingId() == recordingId)
{
found.set(true);
final RecordingState currentState = hDecoder.state();
if (targetState != currentState)
{
if (expectedState != currentState)
{
throw new AeronException("(recordingId=" + recordingId + ") state transition " +
currentState + " -> " + targetState + " is not allowed");
}
he.state(targetState);
out.println("(recordingId=" + recordingId + ") changed state to " + targetState);
}
}
});
if (!found.get())
{
throw new AeronException("no recording found with recordingId: " + recordingId);
}
}
}
private static String validateChecksumClass(final String checksumClassName)
{
final String className = null == checksumClassName ? null : checksumClassName.trim();View on GitHub (pinned to 6d60124e15)