aeron-io/aeron · error · ArchiveEvent

ACTIVE_RECORDING

ACTIVE_RECORDING

Error message

cannot extend active recording ${recordingId} streamId=${streamId} channel=${originalChannel}

What it means

An extendRecording request targeted a recordingId whose recording session is still active in the archive. The archive refuses to extend a recording that has not yet stopped, sends an ACTIVE_RECORDING error to the control session, and throws ArchiveEvent internally. Extension is only valid for completed recordings.

Solutions

  1. Wait until the original recording has stopped (stopRecording called and session removed) before extending.
  2. Poll the archive catalog / use RecordingSignal events to confirm the recording reached STOPPED state.
  3. Use a new recordingId instead of extending if the old recording is still live.
  4. Close lingering publications associated with the original recording so its session can terminate.
  5. Add correlation-based retry with backoff if extend races with recording stop.

Example fix

// before
long recId = archive.startRecording(channel, streamId, SourceLocation.LOCAL);
archive.extendRecording(recId, channel, streamId, SourceLocation.LOCAL); // still active!

// after
long recId = archive.startRecording(channel, streamId, SourceLocation.LOCAL);
archive.stopRecording(subscription);
awaitRecordingStopped(archive, recId); // wait for RecordingSignal.STOP
archive.extendRecording(recId, channel, streamId, SourceLocation.LOCAL);
Defensive patterns

Strategy: validation

Validate before calling

// client-side pre-check via ListRecordingsForUri + recording signals
RecordingSignal last = awaitSignal(archive, recordingId);
if (last != RecordingSignal.STOP) {
    throw new IllegalStateException("recording " + recordingId + " still active; cannot extend");
}

Try / catch

try {
    archive.extendRecording(recId, channel, streamId, SourceLocation.LOCAL);
} catch (ArchiveException e) {
    if (e.errorCode() == ArchiveErrorCode.ACTIVE_RECORDING) {
        // wait for stop, then retry extend
    }
}

Prevention

When it happens

Trigger: Client calls AeronArchive.extendRecording()/startRecording with a replay/extend subscription whose recordingId still has a live RecordingSession in recordingSessionByIdMap (recording not stopped).

Common situations: Application reuses a recordingId that is still being written to; race where the recording stop has not completed before extend is issued; re-running an extend operation on the same recording twice; configuration where the original recording publication is still open (e.g. unclosed publication keeps the recording active).

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/4fe948d11f17bcc3. Report an issue: GitHub.

Appendix: source

Thrown at aeron-archive/src/main/java/io/aeron/archive/ArchiveConductor.java:2076

    private void extendRecordingSession(
        final ControlSession controlSession,
        final long correlationId,
        final long recordingId,
        final String strippedChannel,
        final String originalChannel,
        final Image image,
        final boolean autoStop)
    {
        final long subscriptionId = image.subscription().registrationId();
        try
        {
            if (recordingSessionByIdMap.containsKey(recordingId))
            {
                final String msg = "cannot extend active recording " + recordingId +
                    " streamId=" + image.subscription().streamId() + " channel=" + originalChannel;
                controlSession.sendErrorResponse(correlationId, ACTIVE_RECORDING, msg);
                throw new ArchiveEvent(msg);
            }

            catalog.recordingSummary(recordingId, recordingSummary);

            final DeleteSegmentsSession deleteSegmentsSession = deleteSegmentsSessionByIdMap.get(recordingId);
            if (null != deleteSegmentsSession &&
                deleteSegmentsSession.maxDeletePosition() >= recordingSummary.stopPosition)
            {
                final String msg = "cannot extend recording " + recordingId +
                    " due to an outstanding delete operation: streamId=" +
                    image.subscription().streamId() + " channel=" + originalChannel;
                controlSession.sendErrorResponse(correlationId, GENERIC, msg);
                throw new ArchiveEvent(msg);
            }

            validateImageForExtendRecording(correlationId, controlSession, image, recordingSummary);

            final Counter position = RecordingPos.allocate(

View on GitHub (pinned to 6d60124e15)