Repository navigation
Android: ColorOS blocks the test-only snapshot helper as a "PC install attack" (-99), removing all snapshot-based commands #2364
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 <helper.apk> # no -t
Failure [INSTALL_FAILED_TEST_ONLY]
$ adb install -r -t <helper.apk> # with -t
Failure [-99]
$ adb push <helper.apk> /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
- 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 fix(android): actionable error when the snapshot helper is unavailable (#1284) #1285 is already good at saying what failed; naming this specific OEM class would say why.
- 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. - Consider an env var for a user-supplied helper artifact.
adbProvider.snapshotHelperArtifactalready exists for cloud providers; exposing an equivalent for local runs would let affected users supply a locally-repackaged helper without patchingnode_modules. I checked theAGENT_DEVICE_*surface and did not find one.
Happy to test any of these on the device — I have it available.
Activity
thymikee commentedon Sep 14, 2026
Thanks for the thorough report, the ruled-out table saved us real time.
Good news:
testOnlywas a leftover from when the helper wasdebuggable, not something the helper needs. Instrumentation can only be started from shell viaam instrument, and the session server binds loopback, so the flag protects nothing here. That means we can drop it (and the-tinstall flag) outright instead of adding a variant or an escape hatch. #2603 does that for both the snapshot and IME helpers. On a stock API 36 emulator both helpers now install with plainadb install -r, andsnapshot/click/fillrun end to end through the daemon.We don't have a ColorOS device, so you're the only one who can close the loop. Would you be up for building from that branch and trying it on the Find N6?
If
adb installstill returns-99, the same logcat lines you pasted (PackageInstaller: PC install attack detected) would tell us whether the check also trips on the debug signing certificate, which is the one remaining unknown and would give us a concrete next lever. If you'd rather not set up the Android build tools, say so and we'll share a prebuilt APK from the branch.AliceLJY commentedon Sep 15, 2026
Confirmed working on the Find N6 (ColorOS). Thanks for the quick turnaround.
One thing that saves the build step: the fix shipped in npm
0.21.3(published ~30 min after #2603 merged), and the published package ships the helper APKs directly underandroid/*/dist/, so there's no need to build from the branch. I verified the released artifact before installing:agent-device-android-snapshot-helper-0.21.3.manifest.json->"installArgs": ["install", "-r"], no-taapt2 dump xmltreeon both the snapshot and IME helper APKs: notestOnlyattributeResult after
npm i -g agent-device@latest(0.20.10 -> 0.21.3):adb -s <serial> install -r <snapshot-helper.apk>(no-t): no more-99, no "PC install attack" line in logcatsnapshot -i --json->success: true,androidSnapshot.backend: "android-helper",helperVersion 0.21.3,helperTransport: "persistent-session",snapshotQuality: {state: "healthy"}, 315 nodes in 281 msinstallReason: "current", ~1 s wall clockadb install -r <ime-helper.apk>->Success, and installing it does not enable it (settings get secure enabled_input_methodsstill lists only my normal IME). I did not exercisefillitself, since activating the helper IME is device-global and this is my daily phone — but the install half of that path is no longer blocked either.One finding you can't see on an emulator, in case it's worth a line in the docs or the timeout hint: on ColorOS the first install of a given package still opens the system
PackageInstallerActivityflow on the device, and it does so per package — both helpers needed it. It is two taps rather than one: confirm the install, then dismiss the completion screen.The very first
snapshot -ireturnedadb timed out after 30000mswith empty stdout/stderr andadbFailure: "timeout". That was adb waiting on the dialog, not a rejection —dumpsys windowshowed the focus sitting oncom.android.packageinstaller/.PackageInstallerActivity. Once it is dismissed the install completes and every subsequent run is instant. The IME helper returned a plainSuccessonly because the screen was being watched and both taps happened immediately — which also means I cannot tell you which of the two unblocksadb install, since both landed well inside the 30 s window.So an unattended first run will stall there until it times out, and the current "adb server may be wedged / run
adb kill-server" hint points away from the real cause in this case. Something like "if this is the first install of this helper, check the device for a system install-confirmation dialog" would have saved me a round of guessing.refactor(android): drop the snapshot helper manifest installArgs (#2618)