{"record":{"id":"aa7945d2a73ca9a3","repo":"aeron-io/aeron","slug":"failed-to-read-fragment-header","errorCode":null,"errorMessage":"failed to read fragment header","messagePattern":"failed to read fragment header","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"aeron-archive/src/main/java/io/aeron/archive/ReplaySession.java","lineNumber":630,"sourceCode":"            }\n        }\n\n        fileChannel = FileChannel.open(segmentFile.toPath(), FILE_OPTIONS);\n    }\n\n    static boolean notHeaderAligned(\n        final FileChannel channel,\n        final UnsafeBuffer buffer,\n        final int segmentOffset,\n        final int termOffset,\n        final int termId,\n        final int streamId) throws IOException\n    {\n        final ByteBuffer byteBuffer = buffer.byteBuffer();\n        byteBuffer.clear().limit(HEADER_LENGTH);\n        if (HEADER_LENGTH != channel.read(byteBuffer, segmentOffset))\n        {\n            throw new IOException(\"failed to read fragment header\");\n        }\n\n        return isInvalidHeader(buffer, streamId, termId, termOffset);\n    }\n\n    private void state(final State newState, final String reason)\n    {\n        logStateChange(state, newState, sessionId, recordingId, replayPosition, null == reason ? \"\" : reason);\n        state = newState;\n    }\n\n    @SuppressWarnings(\"unused\")\n    private void logStateChange(\n        final State oldState,\n        final State newState,\n        final long sessionId,\n        final long recordingId,\n        final long position,","sourceCodeStart":612,"sourceCodeEnd":648,"githubUrl":"https://github.com/aeron-io/aeron/blob/6d60124e15e35c11b49ba2e3c2c2858a09a18803/aeron-archive/src/main/java/io/aeron/archive/ReplaySession.java#L612-L648","documentation":"When opening a recording segment for replay, ReplaySession reads the first HEADER_LENGTH bytes of the segment file and throws this IOException if the read returns fewer bytes than a full fragment header. This means the segment file is missing data at the expected offset, so the replay cannot proceed safely — the file is truncated or the offset is beyond the written data.","triggerScenarios":"notHeaderAligned (called during init when positioning the replay) issues channel.read(byteBuffer, segmentOffset) on the segment file channel and the read returns less than HEADER_LENGTH. Happens when segmentOffset points at or past the end of a short/truncated segment file, or when the replay position was advanced into a region that was never written (e.g. live replay racing an active recording, or a partially flushed segment).","commonSituations":"Replaying a recording whose last segment was truncated by a crash or unclean shutdown; replaying a live recording from the very end where the segment has only a partial header written; replaying from a copied/mirrored archive directory that was copied mid-write; wrong segment length configuration so offsets land outside the file.","solutions":["Verify the segment file size on disk; if it is truncated or corrupt, restore it from a backup or re-record the data","If replaying a live recording, request a position within the recorded data (e.g. from startPosition) rather than the very end, or retry after more data is written","Check that the archive's segmentFileLength configuration matches the value used when the recording was created","Catch IOException in the replay consumer and re-subscribe to the replay or fall back to the last known-good position"],"exampleFix":"// before: replay immediately at the live end of an active recording\nlong pos = recordingPositionCounter.get();\narchive.startReplay(recordingId, pos, Long.MAX_VALUE);\n\n// after: replay only fully written data, e.g. joined position\nlong pos = joinedPosition; // or wait until recordingPosition > requested pos\narchive.startReplay(recordingId, Math.min(pos, recordingPositionCounter.get()), len);","handlingStrategy":"retry","validationCode":"File seg = new File(archiveDir, segmentFileName);\nif (!seg.exists() || seg.length() < HEADER_LENGTH) {\n    throw new IllegalStateException(\"segment too short/truncated: \" + seg);\n}","typeGuard":"static boolean segmentIsReadable(FileChannel ch, long segmentOffset) throws IOException {\n    return ch.size() - segmentOffset >= HEADER_LENGTH;\n}","tryCatchPattern":"try {\n    subscription = archive.startReplay(recordingId, position, length, channel, streamId);\n} catch (IOException e) {\n    if (e.getMessage().contains(\"failed to read fragment header\")) {\n        // truncated/partially written segment: retry from a safe position after recording progresses\n        long safePos = recordingPositionCounter.get() - segmentLength;\n        retryReplay(recordingId, Math.max(safePos, startPosition));\n    } else { throw e; }\n}","preventionTips":["Avoid replaying a live recording from the extreme end where segments may be only partially written","Shut archives down cleanly so segment files are fully flushed; treat post-crash segments as suspect","Keep segmentFileLength consistent between recording and replay configurations","Only copy/restore archive directories while the archive is quiesced"],"tags":["archive","replay","io","truncated-file"],"backgroundTag":"file-read-failed","analyzedSha":"6d60124e15e35c11b49ba2e3c2c2858a09a18803","analyzedAt":"2026-09-12T11:17:07.683Z","contentChangedAt":"2026-09-12T11:17:07.683Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}