callstack / callstack/agent-device
Android: ColorOS blocks the test-only snapshot helper as a "PC install attack" (-99), removing all snapshot-based commands
- Dominant language
- TypeScript
- Stars
- 4.6k
- Forks
- 299
- Avg merge
- 10h 17m
- Merged PRs (30d)
- 515
Description
## Summary
On ColorOS (OPPO), the bundled Android snapshot helper cannot be installed. The device's package installer classifies the install as a **"PC install attack"** and fails the session with vendor code `-99`, so every `snapshot`-dependent command is unusable on these devices.
This is **not** a blanket adb-install restriction: normal (non-test-only) APKs install over the same adb connection without any prompt. The block is specific to APKs carrying `android:testOnly="true"`, which the snapshot helper does.
Filing this because #1284 / #1285 deliberately chose hard-fail over the stock-UIAutomator fallback. That decision is sound, but it means an OEM that blocks test-only packages removes the entire Android capture surface with no supported escape hatch.
## Environment
| | |
|---|---|
| agent-device | 0.20.10 (global npm install) |
| Host | macOS 27.0, Node 22+ |
| Device | OPPO Find N6 (`PLP110`), ColorOS, `targetSdk=36`, physical device over USB |
| adb | 1.0.41 / 37.0.1 |
Screen was on and unlocked throughout. USB debugging authorized (`adb devices` reports `device`, not `unauthorized`).
## Reproduction
```
$ agent-device snapshot -i --json
Android snapshot helper failed: Failed to install Android snapshot helper:
failed to install .../agent-device-android-snapshot-helper-0.20.10.apk:
Failure [INSTALL_FAILED_TEST_ONLY]
```
Installing the same artifact by hand reproduces it and shows the vendor code:
```
$ adb install -r # no -t
Failure [INSTALL_FAILED_TEST_ONLY]
$ adb install -r -t # with -t
Failure [-99]
$ adb push /data/local/tmp/ && adb shell pm install -r -t /data/local/tmp/helper.apk
Failure [-99]
```
## Root cause (logcat)
```
E OplusCustomizePackageManagerService: getAdbInstallUninstallDisabled start
D PackageManager: send package verification broadcast
D PackageManager: installStage allowInterceptAdbInstallInInstallStage pkg:com.callstack.agentdevice.snapshothelper
I PackageInstaller: PC install attack detected!com.callstack.agentdevice.snapshothelper
D PackageInstallerSession: Marking session as failed: -99
D PackageInstallerSessionExtImpl: uploadInstallFailInfos:{code=20010,
initiatingPackageName=com.android.shell, targetSdk=36,
app_pkg=com.callstack.agentdevice.snapshothelper, statusCode=-99}
```
`-99` is not an AOSP `PackageManager.INSTALL_FAILED_*` constant; it comes from OPPO's interception layer (`OplusCustomizePackageManagerService`, `allowInterceptAdbInstallInInstallStage`).
## What I ruled out
| Hypothesis | Test | Result |
|---|---|---|
| Corrupt/truncated APK | `unzip -t` on the bundled artifact | Passes, no errors |
| Screen locked | `dumpsys window` during install | `mScreenOn=true`, `mScreenLocked=false` |
| AOSP adb-install verification | Set `verifier_verify_adb_installs` 1 → 0, retry | Still `-99` (restored to 1 afterwards) |
| **adb installs blocked wholesale** | **Pulled an already-installed, release-signed app off the device and reinstalled it with `adb install -r`** | **`Success`** |
That last row is the decisive one: the same adb channel installs a normal APK fine. Only the `testOnly` flag triggers the vendor block.
## What still works without the helper
Verified on the same device with a session open: `open` / `close`, `install` / `reinstall`, `screenshot` (produced a 1140x2616 PNG), `logs`, `apps`. Only `snapshot` and everything built on it (`click`, `fill`, `find`, `wait`, `diff`) are unavailable.
## Suggestions
1. **Document it.** A line in the Android installation docs — "some OEM builds (ColorOS observed) reject test-only packages from adb; the snapshot helper cannot be installed on those devices" — would save the diagnostic time. The current hint from #1285 is already good at saying *what* failed; naming this specific OEM class would say *why*.
2. **Consider an opt-in non-test-only helper build.** If the helper did not set `android:testOnly="true"`, it would install on these devices. I understand the flag is protective (it keeps the helper off Play and out of normal install flows), so this would need to be an explicit, clearly-labelled variant rather than the default.
3. **Consider an env var for a user-supplied helper artifact.** `adbProvider.snapshotHelperArtifact` already exists for cloud providers; exposing an equivalent for local runs would let affected users supply a locally-repackaged helper without patching `node_modules`. I checked the `AGENT_DEVICE_*` surface and did not find one.
Happy to test any of these on the device — I have it available.
Contributor guide
Assessment
This issue has not been assessed yet.