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

  1. Check the recording's current state first (describeRecording) and only mark when it matches the expected source state.
  2. Skip the operation if the recording is already in the target state (the tool only throws when states differ AND don't match expected).
  3. 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

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


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)