Skip to content

reinstall-app: close the destructive window entirely by installing before uninstalling #675

Description

@filip131311

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 installclear 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.

Why it wasn't shipped in #674

It changes the mechanism, not just the ordering:

  1. 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.
  2. 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.
  3. ios-remote should NOT reorder. simctlInstall uploads 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.
  4. 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) → uninstallinstall. An APFS cp -Rc clone of the bundle costs ~0.007s for 94 MB if the second install needs a stable source.
  • ios-remote / Vega: keep fix(reinstall-app): validate the artifact before uninstalling anything #674's static validation.
  • Keep the static checks everywhere regardless — they give a fast, well-worded failure — but demote them from "the fix" to "better errors".

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions