Skip to content

fix(reinstall-app): validate the artifact before uninstalling anything - #674

Open
filip131311 wants to merge 2 commits into
mainfrom
filip/reinstall-validate-before-uninstall
Open

fix(reinstall-app): validate the artifact before uninstalling anything#674
filip131311 wants to merge 2 commits into
mainfrom
filip/reinstall-validate-before-uninstall

Conversation

@filip131311

Copy link
Copy Markdown
Collaborator

Fixes #625. This is the one issue in the sweep that destroys user state, so the PR body is explicit about what it does and does not cover.

Reproduced on the released build

BEFORE:  package:com.argent.androiddevtools

$ argent run reinstall-app --udid emulator-5554 \
    --bundleId com.argent.androiddevtools --appPath <install>/package.json

[Tool:reinstall-app] adb ... install ... failed:
    adb: filename doesn't end .apk or .apex

AFTER:   *** package GONE ***

adb rejected the file on a filename check — the cheapest validation there is — but argent had already uninstalled the app, and the uninstall takes the app's data with it. All four platform impls share the shape: resolve → uninstall (swallowed) → install, with nothing checked in between. The tool's own description already promised the opposite.

The fix

Each platform now validates while the current installation is still on the device. Everything is a local stat or a 4-byte read — nothing touches the device.

target checks
iOS exists; is a directory; ends .app; Info.plist at the root (which also rejects a macOS bundle, since those nest it under Contents/)
Android exists; is a file; ends .apk/.apex; starts with a zip header — catches a renamed text file or a truncated build output
Vega exists; is a file; ends .vpkg — no magic check, the container format isn't documented here and guessing a signature risks rejecting valid packages

The extension test is case-insensitive, because adb's is. I verified this: UPPER.APK installs fine. A stricter rule would have rejected artifacts that work today — worth calling out, since "match adb exactly" invites the opposite assumption.

iOS also refuses a path inside the target simulator's own container. That's the reported iOS case and the one place validation alone can't see the problem: the path exists and is a real bundle, but the uninstall deletes the container it lives in, so the install then fails on a path that no longer exists. Checked against the default set and every configured additional set, because deviceSetForUdid returns null both for "the default set" and for a UDID it hasn't seen.

ios-remote gets the structural checks but not the container check — the simulator is on another host, so the local CoreSimulator layout says nothing about it.

Live, after the fix — same call that destroyed the app:

App path ".../package.json" is not an .apk or .apex — adb rejects any other filename, which
would leave the device with no copy of the app. The existing installation was left untouched.

installed AFTER: package:com.argent.androiddevtools   ← survives

What this does NOT fix — stated plainly

A device rejecting a well-formed artifact still destroys the installation: wrong ABI, minSdkVersion, signature mismatch, a device-slice .app, a valid zip whose contents are corrupt.

I hit one of these by accident while verifying, on a genuine artifact: reinstalling argent's own helper APK failed with INSTALL_FAILED_TEST_ONLY (argent never passes adb install -t) — validation correctly passed, the device refused, and the app was gone. That is the honest residual.

And no fix can promise data preservation: the tool exists to clear app data, so the previous state is unrecoverable by design. The description now says both halves.

The stronger fix, measured and deliberately deferred

The destructive window can be closed entirely by reordering, and I measured that it works:

  • adb install -r over an existing install → Success, no uninstall needed
  • a failed install -r → app and data fully intact
  • pm clear → wipes data, app stays installed

So Android could be install -rpm clear: no destructive window, and one install instead of today's uninstall+install. iOS behaves the same way (simctl install over an existing install preserves the container).

I'm not shipping it here because it changes the mechanism, not just the ordering: pm clear revokes runtime permissions where -g grants them, it needs a fallback for INSTALL_FAILED_UPDATE_INCOMPATIBLE (a signature mismatch — common with debug vs release keystores), and ios-remote shouldn't reorder at all since a double install doubles a network upload. That deserves its own review rather than riding on a validation fix. Filed as a follow-up with the measurements.

Checks

  • 3097 tests pass. 11 new, whose key assertion is not "it threw" but "the uninstall was never reached" — that's the regression guard for this bug, and it covers all four impls.
  • vega-reinstall-result.test.ts needed a real temp .vpkg: it used /tmp/app.vpkg, which doesn't exist, so validation correctly rejected it.
  • Also pinned: a first-time install (nothing to uninstall) still works, and an uppercase .APK is still accepted.
  • prettier, eslint, both typechecks clean; extract-tools 46/46; lock untouched.

reinstall-app uninstalls unconditionally and only then hands the path to the
installer, so a bad artifact left the device with no copy of the app — and the
uninstall takes the app's data with it, with no way back. Reproduced on the
released build: passing an existing non-APK made adb reject it on a filename
check, and the package was gone.

All four platform impls now check the artifact first, while the current
installation is still on the device. Everything checked is a local stat or a
four-byte read; nothing touches the device. iOS wants a directory ending .app
with an Info.plist at its root — which also rejects a macOS bundle, since those
keep it under Contents/. Android wants a file ending .apk or .apex that starts
with a zip header, enough to catch a renamed text file or a truncated build
output. Vega wants a .vpkg file; its container format is not documented here, so
guessing a signature would risk rejecting valid packages.

The extension test is case-insensitive because adb's is: UPPER.APK installs
fine, so a stricter rule would reject artifacts that work today.

iOS also refuses a path inside the target simulator's own container. That is the
reported iOS case and the one place where validation alone is not enough to
notice: the path exists and looks like a real bundle, but the uninstall deletes
the container it lives in, so the install then fails on a path that no longer
exists. Checked against the default set and every configured additional set,
because deviceSetForUdid returns null both for the default set and for a UDID it
has not seen.

ios-remote gets the structural checks but not the container check — the
simulator is on another host, so the local CoreSimulator layout says nothing
about it.

What this does not fix: a device rejecting a well-formed artifact. Wrong ABI, a
minimum OS version, a signature mismatch or a test-only APK all still fail after
the uninstall, and the app is still gone. The tool exists to clear app data, so
no amount of validation can promise to preserve it. The description now says
both halves of that.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

reinstall-app uninstalls the app before validating the artifact, so a bad path leaves the device with nothing installed

1 participant