{"record":{"id":"72c3178c947b6421","repo":"aeron-io/aeron","slug":"unexpected-image-close-when-replaying-log-position-position","errorCode":null,"errorMessage":"unexpected image close when replaying log: position=<position>","messagePattern":"unexpected image close when replaying log: position=<position>","errorType":"exception","errorClass":"ClusterEvent","httpStatus":null,"severity":"error","filePath":"aeron-cluster/src/main/java/io/aeron/cluster/ConsensusModuleAgent.java","lineNumber":1860,"sourceCode":"        return new LogReplay(archive, logRecordingId, logPosition, appendPosition, logAdapter, ctx);\n    }\n\n    int replayLogPoll(final LogAdapter logAdapter, final long stopPosition)\n    {\n        int workCount = 0;\n\n        if (ConsensusModule.State.ACTIVE == state || ConsensusModule.State.SUSPENDED == state)\n        {\n            logAdapter.poll(stopPosition);\n            final long position = logAdapter.position();\n\n            if (commitPosition.proposeMaxRelease(position))\n            {\n                workCount++;\n            }\n            else if (logAdapter.isImageClosed() && position < stopPosition)\n            {\n                throw new ClusterEvent(\"unexpected image close when replaying log: position=\" + position);\n            }\n        }\n\n        workCount += consensusModuleAdapter.poll();\n\n        return workCount;\n    }\n\n    long logRecordingId()\n    {\n        return logRecordingId;\n    }\n\n    void logRecordingId(final long recordingId)\n    {\n        if (NULL_VALUE != recordingId)\n        {\n            logRecordingId = recordingId;","sourceCodeStart":1842,"sourceCodeEnd":1878,"githubUrl":"https://github.com/aeron-io/aeron/blob/6d60124e15e35c11b49ba2e3c2c2858a09a18803/aeron-cluster/src/main/java/io/aeron/cluster/ConsensusModuleAgent.java#L1842-L1878","documentation":"While a follower is replaying the log from the archive, the replay image can close prematurely (recording/image ended before reaching stopPosition). The agent throws ClusterEvent to signal the replay cannot be completed, which typically triggers a retry of the log replay or election restart.","triggerScenarios":"During log replay, logAdapter.isImageClosed() becomes true while commit position is still below stopPosition — the archive replay image ended early because the recording stopped or the archive connection dropped.","commonSituations":"Archive recording deleted or stopped before all followers caught up; archive agent/network failure severing the replay subscription; leader crashed and its recording became unavailable mid-replay.","solutions":["Verify the archive recording covering the full log range exists and was not deleted (recording log / aeron-archive tooling).","Retry the election/replay — the cluster usually re-elects and re-requests the replay.","Check archive client and network stability between follower and archive/leader.","Increase archive retention so recordings outlive follower catch-up windows."],"exampleFix":"// before: recordings pruned aggressively\narchive.markCatalog... delete old recordings while followers still need them\n// after: retain recordings until all followers have caught up past that position","handlingStrategy":"retry","validationCode":"// before requesting replay\nif (!archive.hasRecordingForRange(logPosition, stopPosition)) { electDifferentLeader(); }","typeGuard":null,"tryCatchPattern":"try { agent.doWork(); } catch (ClusterEvent e) { if (e.getMessage().contains(\"unexpected image close when replaying log\")) { requestReplayAgain(); } else { throw e; } }","preventionTips":["Retain archive recordings until all followers pass their positions.","Monitor archive recording health on the leader.","Ensure reliable networking between followers and archive."],"tags":["archive","replay","image-closed","replication"],"backgroundTag":"unexpected-response-shape","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"}