Tencent/tinker · critical · IOException
old dex signature mismatch! expected: %s, actual: %s
Error message
old dex signature mismatch! expected: %s, actual: %s
What it means
IOException from DexPatchApplier.executeAndSaveTo: the SHA-1 signature computed over the old dex does not match the old-dex signature stored in the patch file at generation time. This is the applier's core safety check — the patch was diffed against different bytes than the dex being patched.
Source
Thrown at tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/DexPatchApplier.java:135
// old dex we passed in.
if (this.patchFile == null) {
throw new IllegalArgumentException("patch file is null.");
}
if (this.patchFile.getVersion() > DexPatchFile.VERSION_02) {
final int oldDexAPI = this.oldDex.getTableOfContents().api;
final int expectedOldDexAPI = this.patchFile.getOldDexAPI();
if (oldDexAPI != expectedOldDexAPI) {
throw new IOException("old dex version mismatch! expetced: "
+ expectedOldDexAPI + ", actual: " + oldDexAPI);
}
}
byte[] oldDexSign = this.oldDex.computeSignature(false);
if (oldDexSign == null) {
throw new IOException("failed to compute old dex's signature.");
}
byte[] oldDexSignInPatchFile = this.patchFile.getOldDexSignature();
if (CompareUtils.uArrCompare(oldDexSign, oldDexSignInPatchFile) != 0) {
throw new IOException(
String.format(
"old dex signature mismatch! expected: %s, actual: %s",
Arrays.toString(oldDexSign),
Arrays.toString(oldDexSignInPatchFile)
)
);
}
// Firstly, set sections' offset after patched, sort according to their offset so that
// the dex lib of aosp can calculate section size.
TableOfContents patchedToc = this.patchedDex.getTableOfContents();
patchedToc.api = patchFile.getPatchedDexAPI();
patchedToc.header.off = 0;
patchedToc.header.size = 1;
patchedToc.mapList.size = 1;
patchedToc.stringIds.offView on GitHub (pinned to 1b7ea02c23)
Solutions
- Regenerate the patch from the exact base apk currently installed/distributed
- Verify channel/build variants match between diff time and apply time
- Add an automated base-apk hash check in CI before releasing patches
Defensive patterns
Strategy: validation
Validate before calling
byte[] actual = oldDex.computeSignature(false);
if (actual == null || CompareUtils.uArrCompare(actual, patchFile.getOldDexSignature()) != 0) {
throw new IOException("base dex differs from patch target; regenerate patch");
} Try / catch
try {
applier.executeAndSaveTo(out);
} catch (IOException e) {
if (String.valueOf(e.getMessage()).startsWith("old dex signature mismatch")) {
// halt rollout: installed base differs from patch base
} else { throw e; }
} Prevention
- Record and check base apk hash in your patch delivery metadata
- Never rebuild/re-sign the base apk between patch generation and distribution
When it happens
Trigger: Applying a dex patch to any old dex whose content differs by even one byte from the dex used by tinker-patch when the patch was produced.
Common situations: Base apk re-built (even trivially) after patch generation; channels appending data to the dex; wrong base apk version in a multi-flavor setup; mixing debug and release artifacts.
Related errors
- old dex version mismatch! expetced:
- failed to compute old dex's signature.
- bad patch operation sequence. addCounter: %d, addCount: %d,
- patch file is null.
- Illegal dex format, there's at least one loop in class inher
AI-assisted analysis of Tencent/tinker@1b7ea02c23 (2026-08-14).
Data as JSON: /api/errors/06c4b552e96d4612.
Report an issue: GitHub.