fix(android): actionable error when the snapshot helper is unavailable (#1284)#1285
Conversation
Size Report
Startup median (7 runs, lower is better):
Top changed chunks:
|
|
Review finding at The missing-artifact hint describes the wrong required/package contents. It tells source users that a complete helper dist requires Please make the recovery guidance describe the actual runtime contract, for example: run The new install-failure detail and device-side framing otherwise look correct and preserve classified ADB hints. This PR is still draft and iOS smoke was pending at review time. |
… files Review correction on #1285: resolveAndroidSnapshotHelperArtifact only fs.access'es the versioned .manifest.json and the .apk it references — the sha256 is a manifest field, and *.idsig is excluded from the npm package by design. The hint no longer claims the full sidecar set is required, and the test now rejects any future idsig claim.
|
Corrected in 426c975. Verified against New hint:
The test now asserts |
|
Re-review at exact head
The corrected missing-artifact guidance now accurately names the manifest plus referenced APK, and excluding This branch also predates merged #1281, which changed the same helper/install/snapshot files. Although the merge is currently conflict-free and existing CI is green, that CI did not validate the combined tree; please update onto current main and rerun the relevant checks after addressing the findings. Not ready for |
#1284) #1284 kept the hard-fail from #1217 (a silent stock-UIAutomator fallback produced a materially different, app-window-only capture) but asked for actionable hints on both failure modes: - Artifact missing on disk: hint now names the exact `pnpm build:android` command, the full dist file set it produces, and notes packaged installs ship it via prepack. - Artifact present but the device rejects the install (adb/OEM policy): the underlying adb error already surfaces in the message; the hint now explicitly frames this as a device-side failure distinct from a missing build artifact, tagged via a new androidSnapshotHelperInstallFailure detail set at the adb install call site.
… files Review correction on #1285: resolveAndroidSnapshotHelperArtifact only fs.access'es the versioned .manifest.json and the .apk it references — the sha256 is a manifest field, and *.idsig is excluded from the npm package by design. The hint no longer claims the full sidecar set is required, and the test now rejects any future idsig claim.
…ty (#1285 review) P1: the device-side install marker now covers the whole install phase. ensureAndroidSnapshotHelper previously tagged only a resolved nonzero install result; an AndroidAdbProvider.install rejection (enriched INSTALL_FAILED_* AppError from the provider funnel) bypassed the marker and fell back to the generic retry/doctor hint. Both paths now flow through markAndroidSnapshotHelperInstallFailure, which mutates details in place so the original code, message, hint, details, and cause all survive. Regression covers the public daemon snapshot route with a real request handler and an injected provider whose install rejects. P2: androidSnapshotHelperCaptureError rewrapped through normalizeError, which lifts diagnosticId/logPath out of details — the rewrap dropped them (ADR 0010 violation). They are now reinstated into the rewrapped error's details. Hint selection moved to a helper to keep the function under the complexity gate.
426c975 to
dba7393
Compare
|
Both findings addressed at dba7393, on top of a rebase onto current main (1a1ef7c, includes #1281 — verified my diff file-by-file against the new base; the #1281 changes to P1 — install-phase tagging now covers both paths. The entire install phase in
P2 — diagnostic identity survives the rewrap. New tests:
Gates on the rebased tree: typecheck / oxlint / format:check / fallow clean; |
|
Current-head re-review ( One ADR 0010 wire-contract blocker remains: Please preserve all lifted wire fields—at least |
…ap (#1285 review) normalizeError hoists hint, diagnosticId, logPath, retriable, and supportedOn out of details (ADR 0010); the capture rewrap restored only diagnosticId/logPath, so a transient-classified install rejection (e.g. connection_dropped) lost its structured retriable signal on the public daemon error. liftedDiagnosticIdentity is generalized to liftedWireFields covering the complete hoisted set (hint stays owned by the capture hint selector). Public route regression: a retriable provider install rejection keeps error.retriable === true on the daemon snapshot response.
|
Addressed at 1089e7c. Complete lifted-field restore. Extended public regression (same file, real Gates: typecheck / oxlint / format:check / fallow clean. |
|
Current-head re-review ( |
|
Decision
#1284 decided (option a): keep the hard-fail introduced by #1217 (removing the stock-UIAutomator fallback), but make the error actionable. The old silent fallback produced a materially different capture (app-window-only, no systemui/IME, different ordering) and that silent difference caused repeated investigation confusion — failing loudly is intentional, not a bug.
This PR only changes error reporting for the two failure modes; it does not touch capture logic.
Before / after
Mode (a) — helper artifact missing on disk (dev build without
pnpm build:android, or a broken packaged install):Before:
After:
Mode (b) — artifact present, device rejects the install (adb install rejection, OEM policy):
Before:
(or, for unrecognized adb output, the generic "Retry once... run agent-device doctor" fallback — with nothing calling out that the artifact itself was fine)
After:
The underlying adb error was already surfaced in the message (via existing exec-failure enrichment); what was missing was an explicit signal that this is not the same failure class as a missing build artifact.
Implementation
The two modes were already on separate code paths (
resolveAndroidSnapshotHelperArtifact'sfs.accesschecks vs. the install/capture try/catch incaptureAndroidUiHierarchyWithHelper), so no new detection logic was needed to distinguish them. The change:src/platforms/android/snapshot-helper-install.ts: tags a nonzero-exitadb installresult withandroidSnapshotHelperInstallFailure: truein the thrownAppError's details.src/platforms/android/snapshot.ts:androidSnapshotHelperUnavailableError(mode a) gets the richer hint;androidSnapshotHelperCaptureError(mode b) checks the new detail flag and appends the device-side framing while preserving any adb-specific classified hint (e.g.INSTALL_FAILED_UPDATE_INCOMPATIBLE, insufficient storage).Tests
src/platforms/android/__tests__/snapshot-helper.test.ts:ensureAndroidSnapshotHelpertags a device-side install rejection distinctly from artifact resolution.src/platforms/android/__tests__/snapshot.test.ts:snapshotAndroidgives an actionable hint when the helper artifact is missing on disk (assertspnpm build:android,prepack, and the full dist file set appear in the hint).snapshotAndroiddistinguishes a device-side install rejection from a missing build artifact (asserts the adb error and "device-side install failure" appear, andpnpm build:androiddoes not).Test plan
pnpm typecheckpnpm exec oxlint --deny-warningspnpm format:checkpnpm check:affected --base origin/main --run(147/147 affected files pass; the only failures in the subsequent full-suite coverage run wereclient-metro.test.tsandruntime-hints.test.ts, both unrelated to this change and confirmed to pass cleanly in isolation — known flaky under CPU contention)Links: #1284, #1217