MuntashirAkon/AppManager · error · IOException

premature end of tar archive. Didn't find any entry after…

Error message

premature end of tar archive. Didn't find any entry after PAX header.

What it means

Thrown by paxHeaders() when, after reading a per-file PAX extended header (typeflag 'x'), the next call to getNextEntry() returns null — meaning the archive ended without the real entry that the PAX header describes.

Solutions

  1. Verify the archive is complete (size/hash check) and re-download if not
  2. Only read archives after the producer has finished writing them
  3. Catch IOException around getNextTarEntry and report a truncated archive
  4. Re-create the archive with GNU tar/bsdtar if the source is known-bad

Example fix

// before
TarArchiveEntry e = tin.getNextTarEntry();
// after
try {
    TarArchiveEntry e = tin.getNextTarEntry();
    // process
} catch (IOException ex) {
    throw new IOException("Truncated archive: entry missing after PAX header", ex);
}
Defensive patterns

Strategy: try-catch

Try / catch

try {
    while ((e = tin.getNextTarEntry()) != null) { process(e); }
} catch (IOException e) {
    if (e.getMessage() != null && e.getMessage().contains("premature end")) {
        throw new IOException("Archive truncated after PAX header", e);
    }
    throw e;
}

Prevention

When it happens

Trigger: A PAX 'x' header block is followed by end-of-archive markers or EOF instead of the actual file entry — typically a truncated download or a tar cut short while being written.

Common situations: Interrupted downloads/uploads; streaming a tar that is still being produced; archives truncated by disk-full during copy.

Related errors


AI-assisted analysis of MuntashirAkon/AppManager@0152f468fc (2026-09-12). Data as JSON: /api/errors/0c96b46e28a82283. Report an issue: GitHub.

Appendix: source

Thrown at app/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveInputStream.java:600

     *
     * For PAX Format 1.X:
     * The sparse map itself is stored in the file data block, preceding the actual file data.
     * It consists of a series of decimal numbers delimited by newlines. The map is padded with nulls to the nearest block boundary.
     * The first number gives the number of entries in the map. Following are map entries, each one consisting of two numbers
     * giving the offset and size of the data block it describes.
     * @throws IOException
     */
    private void paxHeaders() throws IOException {
        List<TarArchiveStructSparse> sparseHeaders = new ArrayList<>();
        final Map<String, String> headers = TarUtils.parsePaxHeaders(this, sparseHeaders, globalPaxHeaders, entrySize);

        // for 0.1 PAX Headers
        if (headers.containsKey("GNU.sparse.map")) {
            sparseHeaders = TarUtils.parsePAX01SparseHeaders(headers.get("GNU.sparse.map"));
        }
        getNextEntry(); // Get the actual file entry
        if (currEntry == null) {
            throw new IOException("premature end of tar archive. Didn't find any entry after PAX header.");
        }
        applyPaxHeadersToCurrentEntry(headers, sparseHeaders);

        // for 1.0 PAX Format, the sparse map is stored in the file data block
        if (currEntry.isPaxGNU1XSparse()) {
            sparseHeaders = TarUtils.parsePAX1XSparseHeaders(inputStream, recordSize);
            currEntry.setSparseHeaders(sparseHeaders);
        }

        // sparse headers are all done reading, we need to build
        // sparse input streams using these sparse headers
        buildSparseInputStreams();
    }

    private void applyPaxHeadersToCurrentEntry(final Map<String, String> headers, final List<TarArchiveStructSparse> sparseHeaders) {
        currEntry.updateEntryFromPaxHeaders(headers);
        currEntry.setSparseHeaders(sparseHeaders);
    }

View on GitHub (pinned to 0152f468fc)