You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Follow-up to #625 / PR #674, which validates the artifact before the uninstall. That fixes the reported cases but leaves a residual: a well-formed artifact the device rejects still destroys the installation.
Reordering removes the window completely, and I measured that it works.
Measurements (Pixel_9 emulator, API 36; iPhone 17 Pro sim)
Android
adb install -r -d -g <apk> over an existing install → Success (no uninstall needed)
adb install -r -d -g <bad> while app is installed → fails, app AND data fully intact
– filename case: "adb: filename doesn't end .apk or .apex"
– corrupt case: INSTALL_PARSE_FAILED_NOT_APK
adb shell pm clear <pkg> → ~0.075s, wipes data, app stays installed
iOS
simctl install over an existing install → preserves the data container
(note: the container PATH is renamed on every install, so re-probe with
get_app_container rather than caching it — this cost me a false negative)
bad artifacts while installed (non-bundle, missing Info.plist, nonexistent)
→ all fail with the app and data intact
So install → clear has no destructive window, and on Android it is one install where today there are an uninstall plus an install — cheaper than the current flow.
It also closes four of the five residuals #674 documents: corrupt-zip-with-valid-header, wrong ABI, minSdkVersion, and insufficient storage. And on Android it removes the uninstall-A-install-B hazard for free, since a mismatched bundleId would install B and clear B, leaving A untouched.
Permission semantics.pm clear revokes runtime permissions; today's -g grants them. Note the tool's own description says "app data and runtime permissions are cleared", so install-then-clear arguably matches the documented contract better than the current behaviour — but it is an observable change and should be a decision, not a side effect.
Signature mismatch needs a fallback. Verified: re-signing an APK with a different keystore gives INSTALL_FAILED_UPDATE_INCOMPATIBLE and the old app stays installed; uninstall-then-install then succeeds. So install-first needs a fallback on UPDATE_INCOMPATIBLE / PERMISSION_MODEL_DOWNGRADE → uninstall → install. That fallback is safe: adb only reaches the signature check after parsing and verifying the APK (a corrupt file fails earlier as INSTALL_PARSE_FAILED_NOT_APK), so by the time you see it, the artifact is proven installable. This is a common case — debug vs release keystore, or a build from a different machine.
Vega is unverified.vega device install-app over-existing semantics are undocumented and no VVD was available. Don't extend the reorder on speculation.
Also worth noting
reinstall-app never passes adb install -t, so it cannot install a test-only APK at all — it fails with INSTALL_FAILED_TEST_ONLY after the uninstall. Small, but it is how I hit the residual on a real artifact while verifying #674.
Suggested shape
Android:install -r -d -g → on UPDATE_INCOMPATIBLE/PERMISSION_MODEL_DOWNGRADE fall back to uninstall+install → then pm clear.
iOS:simctl install (validates cheaply, ~0.2s, and survives the self-referential case on its own) → uninstall → install. An APFS cp -Rc clone of the bundle costs ~0.007s for 94 MB if the second install needs a stable source.
Follow-up to #625 / PR #674, which validates the artifact before the uninstall. That fixes the reported cases but leaves a residual: a well-formed artifact the device rejects still destroys the installation.
Reordering removes the window completely, and I measured that it works.
Measurements (Pixel_9 emulator, API 36; iPhone 17 Pro sim)
Android
iOS
So
install→clearhas no destructive window, and on Android it is one install where today there are an uninstall plus an install — cheaper than the current flow.It also closes four of the five residuals #674 documents: corrupt-zip-with-valid-header, wrong ABI,
minSdkVersion, and insufficient storage. And on Android it removes the uninstall-A-install-B hazard for free, since a mismatchedbundleIdwould install B and clear B, leaving A untouched.Why it wasn't shipped in #674
It changes the mechanism, not just the ordering:
pm clearrevokes runtime permissions; today's-ggrants them. Note the tool's own description says "app data and runtime permissions are cleared", so install-then-clear arguably matches the documented contract better than the current behaviour — but it is an observable change and should be a decision, not a side effect.INSTALL_FAILED_UPDATE_INCOMPATIBLEand the old app stays installed; uninstall-then-install then succeeds. So install-first needs a fallback onUPDATE_INCOMPATIBLE/PERMISSION_MODEL_DOWNGRADE→ uninstall → install. That fallback is safe: adb only reaches the signature check after parsing and verifying the APK (a corrupt file fails earlier asINSTALL_PARSE_FAILED_NOT_APK), so by the time you see it, the artifact is proven installable. This is a common case — debug vs release keystore, or a build from a different machine.ios-remoteshould NOT reorder.simctlInstalluploads the bundle over the network; a double install doubles a transfer, and the cost is the upload, not a local copy. Static pre-validation (what fix(reinstall-app): validate the artifact before uninstalling anything #674 does) is the right trade there.vega device install-appover-existing semantics are undocumented and no VVD was available. Don't extend the reorder on speculation.Also worth noting
reinstall-appnever passesadb install -t, so it cannot install a test-only APK at all — it fails withINSTALL_FAILED_TEST_ONLYafter the uninstall. Small, but it is how I hit the residual on a real artifact while verifying #674.Suggested shape
install -r -d -g→ onUPDATE_INCOMPATIBLE/PERMISSION_MODEL_DOWNGRADEfall back to uninstall+install → thenpm clear.simctl install(validates cheaply, ~0.2s, and survives the self-referential case on its own) →uninstall→install. An APFScp -Rcclone of the bundle costs ~0.007s for 94 MB if the second install needs a stable source.