Skip to content

fix(android): read a child that vanished mid-read as gone - #3901

Merged
thymikee merged 1 commit into
mainfrom
fix/android-helper-null-windows
Oct 22, 2026
Merged

thymikee merged 1 commit into
mainfrom
fix/android-helper-null-windows

Conversation

@thymikee

@thymikee thymikee commented Oct 22, 2026 •

Copy link
Copy Markdown
Member

Summary

The Android snapshot helper can throw NullPointerException: ... java.util.List.size() on a null object reference from inside AccessibilityNodeInfo.getChild (framework AccessibilityInteractionClient.checkFindAccessibilityNodeInfoResultIntegrity) when the child's window closes during the read. The fixture's alert accept hits it while the dialog closes. The persistent session reports a capture error, so the host force-kills the session, answers with a one-shot am instrument capture, and keeps the session off for about 40 s. Every capture in that window is slower.

AccessibilityNodeReads.child, the single owner of child reads, now treats that throw as the null that the framework answers for a vanished child. 1 file changed.

The new Android perf suite (#3885 stack) found this as automation/alert-accept → android_snapshot_helper_session_fallback.

Validation

  • Head 8e38852: pnpm check:affected --run --head 8e3885218a passed (exactCommit). The android-helpers check is GitHub-authoritative (Android / Smoke Tests).

  • Device: an API 35 pixel_7 emulator (google_apis arm64) with the fixture Release APK. Each iteration presses automation-open-alert, runs alert wait, then alert accept --debug. The NPE session fallbacks were counted from the request logs:

    • base 9935ef5: 3 of 60, plus 2 of 30 in an earlier run;
    • this head: 0 of 60.

    All 60 accepts were handled in both runs.

  • I logged the framework stack trace with a temporary Log.e that is not in this commit.

🤖 Generated with Claude Code

View guided diff Turn on auto-fix

AccessibilityNodeInfo.getChild can throw a NullPointerException from the
framework's result integrity check when the child's window closes during
the read, for example while an alert dialog closes after `alert accept`.
The persistent helper session reported that as a capture failure, so the
host force-killed the session, answered with a one-shot capture, and kept
the session off for about 40 s. Treat the throw as the null that the
framework answers for a vanished child.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Size Report

Metric Base Current Diff
Installed (including dependencies) 5.53 MB 5.53 MB 0 B
Package (unpacked) 5.53 MB 5.53 MB 0 B
Package (download) 1.68 MB 1.68 MB +29 B

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 38.0 ms 40.7 ms +2.8 ms
CLI --help 78.4 ms 81.6 ms +3.1 ms

@thymikee

Copy link
Copy Markdown
Member Author

The PR is ready at 8e38852. The catch in AccessibilityNodeReads.getChild is the right fix for a child that vanishes mid-read, and I found no problems in the diff.

Not blocking, and you can take or leave these: the fix sits in a static method that calls the final Android AccessibilityNodeInfo directly, so the JVM tests cannot reach it and nothing fails if the catch is removed. A small generic seam that treats a throwing reader as "gone", with an AccessibilityReadBatchTest case for it, would cover this, or you could attach the base and head request-log counts for the 0 of 60 against 3 of 60 emulator run. The catch also covers any NullPointerException from getChild, including one from a null node, so a doc comment saying a null node is a programmer error would help. Reads like child.getChildCount and the bounds in appendNode could hit the same window-closing race, so they are worth a look on the next device run.

CI shows one failure: the lint step reports packages/platform-apple/src/snapshot-route.test.ts is over the line cap, which is the main-only cap tracked in #3890. This diff only touches one Android Java file, so it is unrelated. I ran no device and no Java tests, and I did not check which Android frames can throw here. The emulator numbers are yours. I know of no conflicts. This should merge once #3890 is resolved.

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Oct 22, 2026
@thymikee
thymikee merged commit f7844aa into main Oct 22, 2026
15 of 16 checks passed
@thymikee
thymikee deleted the fix/android-helper-null-windows branch October 22, 2026 08:06
@github-actions

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-10-22 08:07 UTC

thymikee added a commit that referenced this pull request Oct 22, 2026
* origin/main:
  feat(test-app): add a Bluesky-depth heavy feed screen (#3884)
  fix(android): read a child that vanished mid-read as gone (#3901)
  feat(ios): route iPad Simulator snapshots through the host AX bridge (#3895)
thymikee added a commit that referenced this pull request Oct 22, 2026
* origin/main:
  feat(test-app): add a Bluesky-depth heavy feed screen (#3884)
  fix(android): read a child that vanished mid-read as gone (#3901)
  feat(ios): route iPad Simulator snapshots through the host AX bridge (#3895)
thymikee added a commit that referenced this pull request Oct 22, 2026
* origin/main:
  feat(test-app): add a Bluesky-depth heavy feed screen (#3884)
  fix(android): read a child that vanished mid-read as gone (#3901)
  feat(ios): route iPad Simulator snapshots through the host AX bridge (#3895)
thymikee added a commit that referenced this pull request Oct 22, 2026
* origin/main:
  feat(test-app): add a Bluesky-depth heavy feed screen (#3884)
  fix(android): read a child that vanished mid-read as gone (#3901)
  feat(ios): route iPad Simulator snapshots through the host AX bridge (#3895)
  Prepare shared resource ownership and selection for #3325 (#3887)
  refactor(kernel): track held execution locks in keyed-lock (#3879)
  perf(ios): ask a ready runner about surface hosts before spawning probes (#3877)
  fix(ios): keep the bridge while a surface host only lingers (#3873)
  perf(ios): synthesize taps on resolved in-app back controls (#3880)
  fix(macos): open a running app by its own path instead of its bundle id (#3889)
  fix(macos): accept mixed-case bundle ids in open (#3867)
  fix(macos-helper): scroll SwiftUI forms whose rows are scroll-area children (#3888)
thymikee added a commit that referenced this pull request Oct 22, 2026
* origin/main:
  fix(android): read a child that vanished mid-read as gone (#3901)
  feat(ios): route iPad Simulator snapshots through the host AX bridge (#3895)
  Prepare shared resource ownership and selection for #3325 (#3887)
  refactor(kernel): track held execution locks in keyed-lock (#3879)
  perf(ios): ask a ready runner about surface hosts before spawning probes (#3877)
  fix(ios): keep the bridge while a surface host only lingers (#3873)
  perf(ios): synthesize taps on resolved in-app back controls (#3880)
  fix(macos): open a running app by its own path instead of its bundle id (#3889)
  fix(macos): accept mixed-case bundle ids in open (#3867)
  fix(macos-helper): scroll SwiftUI forms whose rows are scroll-area children (#3888)
thymikee added a commit that referenced this pull request Oct 22, 2026
* origin/main:
  feat(test-app): add a Bluesky-depth heavy feed screen (#3884)
  fix(android): read a child that vanished mid-read as gone (#3901)
  feat(ios): route iPad Simulator snapshots through the host AX bridge (#3895)
@thymikee

Copy link
Copy Markdown
Member Author

Applied the non-blocking notes in the follow-up #3905, because this PR is already merged. It adds a nullIfGone seam with an AccessibilityReadBatchTest case, and a doc comment on the null-node case. It also records that getChildCount and getBoundsInScreen need no guard: in the API 35 framework sources both are field reads with no interaction-client call. The request-log counts are already in this PR's description (3 of 60 on base, 0 of 60 on head).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-human Valid work that needs human implementation, judgment, or maintainer merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant