callstack / callstack/agent-device

Android: ColorOS blocks the test-only snapshot helper as a "PC install attack" (-99), removing all snapshot-based commands

Closed
#2,364 0 comments 0 reactions 0 assignees View on GitHub
needs-triage
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.