MuntashirAkon/AppManager · error · IOException

Didn't read the right header magic

Error message

Didn't read the right header magic

What it means

IOException from AndroidBackupHeader.read when the very first header line of the stream is not the 'ANDROID BACKUP' magic string. The header parser requires this exact magic before parsing version/encryption lines.

Solutions

  1. Confirm the input file is a real Android backup starting with the 'ANDROID BACKUP' magic
  2. Do not pass converted TAR files into AndroidBackupHeader — use them directly
  3. Re-obtain the backup file; check for transfer corruption (compare sizes/hashes)
  4. Skip any wrapper/leading bytes only if your pipeline intentionally prepends data

Example fix

// before
extractor.extract(wrongFile); // actually a .tar
// after
try (DataInputStream in = new DataInputStream(new FileInputStream(abFile))) {
    byte[] magic = "ANDROID BACKUP".getBytes("UTF-8");
    byte[] buf = new byte[magic.length];
    in.readFully(buf);
    if (!Arrays.equals(buf, magic)) throw new IOException(abFile + " is not an Android backup");
}
extractor.extract(abFile);
Defensive patterns

Strategy: validation

Validate before calling

byte[] magic = "ANDROID BACKUP".getBytes("UTF-8");
byte[] head = new byte[magic.length];
try (InputStream in = new FileInputStream(abFile)) {
    if (in.read(head) != magic.length || !Arrays.equals(head, magic)) {
        throw new IOException(abFile + " is not an Android backup");
    }
}

Try / catch

try {
    InputStream tar = header.toTar();
} catch (IOException e) {
    if (e.getMessage().contains("header magic")) {
        Log.e(TAG, "Input is not an Android backup stream", e);
    } else throw e;
}

Prevention

When it happens

Trigger: The input stream does not start with 'ANDROID BACKUP' — e.g. the file is a raw TAR, a zip, an already-decrypted stream, or the file is corrupt/truncated at the start.

Common situations: Wrong file passed to the restore path (mixing .ab with converted .tar); backups mangled by editors or FTP text-mode transfers; user renamed a non-backup file to .ab.

Related errors


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

Appendix: source

Thrown at app/src/main/java/io/github/muntashirakon/AppManager/backup/adb/AndroidBackupHeader.java:99

                // okay, it's a version we recognize.  if it's version 1, we may need
                // to try two different PBKDF2 regimes to compare checksums.
                final boolean pbkdf2Fallback = (mBackupFileVersion == 1);

                s = readHeaderLine(backupStream);
                mCompress = (Integer.parseInt(s) != 0);
                s = readHeaderLine(backupStream);
                if (s.equals("none")) {
                    // no more header to parse; we're good to go
                } else if (mPassword != null && mPassword.length > 0) { // AES-256
                    preCompressStream = decodeAesHeaderAndInitialize(mPassword, s, pbkdf2Fallback, backupStream);
                } else {
                    throw new IOException("Archive is encrypted but no password given");
                }
            } else {
                throw new IOException("Wrong header version: " + s);
            }
        } else {
            throw new IOException("Didn't read the right header magic");
        }

        // okay, use the right stream layer based on compression
        return mCompress ? new InflaterInputStream(preCompressStream) : preCompressStream;
    }

    @NonNull
    public OutputStream write(@NonNull OutputStream backupStream) throws Exception {
        // Write the global file header.  All strings are UTF-8 encoded; lines end
        // with a '\n' byte.  Actual backup data begins immediately following the
        // final '\n'.
        //
        // line 1: "ANDROID BACKUP"
        // line 2: backup file format version, currently "5"
        // line 3: compressed?  "0" if not compressed, "1" if compressed.
        // line 4: name of encryption algorithm [currently only "none" or "AES-256"]
        //
        // When line 4 is not "none", then additional header data follows:

View on GitHub (pinned to 0152f468fc)