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
- Confirm the input file is a real Android backup starting with the 'ANDROID BACKUP' magic
- Do not pass converted TAR files into AndroidBackupHeader — use them directly
- Re-obtain the backup file; check for transfer corruption (compare sizes/hashes)
- 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
- Check the magic string before constructing the header parser
- Ensure file extensions (.ab vs .tar) match actual formats
- Compare file hashes after transfers to detect corruption
- Keep wrapper-stream plumbing out of the header parser input
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
- Wrong header version: " + s
- ADB restore is requested but there are no .ab files.
- An uninstallation was necessary but couldn't perform it.
- Android System (android) cannot be backed up.
- Android System (android) cannot be restored.
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)