Skip to content

scroll top reaches the top of Android Settings, then fails with scroll_edge_no_progress #3737

Open

Description

@airowe

Summary

On the Android Settings home screen, scroll top scrolls the list all the way back to the top, then fails after 3 passes with scroll_edge_no_progress. At the top, the nested RecyclerView still reports hiddenContentAbove: true. The edge loop keeps swiping up a list that can't move any further, the surface stays the same, and the stuck check fires. It happens after any downward scroll, not just after scroll bottom.

Environment

  • agent-device main at 4db1b7d (built from source, node bin/agent-device.mjs)
  • Pixel_7_API_36 emulator (Android 16 / API 36, google_apis arm64-v8a), headless
  • macOS, Apple silicon

Steps to reproduce

  1. adb shell am force-stop com.google.android.settings.intelligence && adb shell am force-stop com.android.settings
  2. agent-device open com.android.settings --platform android
  3. agent-device scroll down --platform android --json (or scroll bottom)
  4. agent-device scroll top --platform android --json

Expected

Step 4 reports Scrolled to top with N up passes.

Actual

Step 3 succeeds. Step 4 fails, even though the screen is back at the top:

{
  "code": "COMMAND_FAILED",
  "message": "scroll top moved nothing across 3 passes: the container still reports hidden content but its contents never shifted",
  "details": { "reason": "scroll_edge_no_progress", "edge": "top", "passes": 3, "dispatchedSteps": 3, "dispatched": "unknown" }
}

Screenshots after open, after step 3 and after step 4. The final frame matches the freshly opened screen; in the scroll bottom run the two PNGs are byte-identical. The images and recordings are at the end.

snapshot --json at the top afterwards (scroll containers only):

index type id hiddenContentAbove hiddenContentBelow
11 ScrollView settings_homepage_container – –
18 ScrollView main_content_scrollable_container – true
23 RecyclerView recycler_view true –

It reproduced in every run: 4 of 4 on main (3 after scroll bottom, 1 after scroll down), and the same way on the #3736 branch, which doesn't change this path.

Not yet known

  • Where the stale hiddenContentAbove comes from: the platform (canScrollVertically(-1) on a RecyclerView nested in a scroll view) or agent-device's hint derivation.
  • Whether other apps with a RecyclerView inside a NestedScrollView, or other API levels, show it.

Done when

scroll top on this screen stops at the top and reports success. If the hint can't be trusted for nested scrollers, the edge loop shouldn't report no-progress for a list that visibly reached its edge.

Screenshots and recordings

In order: the scroll down → scroll top screenshots, the scroll bottom → scroll top screenshots, then a recording of each run in the same order.

scroll down → scroll top (after open, after scroll down, after the failed scroll top):

Settings after open, after scroll down, and after the failed scroll top, which is back at the top

scroll bottom → scroll top (after open, after scroll bottom, after the failed scroll top):

Settings after open, after scroll bottom, and after the failed scroll top, which is back at the top

Recording, scroll down → scroll top:

B-down-then-top.mp4

Recording, scroll bottom → scroll top:

A-bottom-then-top.mp4

Activity

  1. airowe commented on Oct 16, 2026

    @airowe
    ContributorAuthor

    Root cause, from the tree on a freshly opened Settings screen that was never scrolled:

    index type id rect hiddenContentAbove hiddenContentBelow
    39 ScrollView main_content_scrollable_container 0,480 1440×2556 – true
    45 RecyclerView recycler_view (child of 39) 0,480 1440×2556 true –

    The flag is in --raw output too, so it's set before any presentation filtering. It comes straight from the platform: the helper emits can-scroll-backward from the node's ACTION_SCROLL_BACKWARD action (AccessibilityTreeXml.java#L89-L91), and androidScrollActionHints maps it to hiddenContentAbove. The nested RecyclerView offers scroll-backward even at the top, while its parent ScrollView, with the identical rect, correctly offers only forward. scroll top picks the RecyclerView, its swipes can't move anything, and the stuck check fires.

    Two possible fixes; I'd like your call before writing one:

    1. Prefer the outer scroller. When a scrollable has the same rect as its scrollable parent, trust the parent's edge hints for scroll top/scroll bottom. It's narrow and only affects this nested setup.
    2. Cross-check with geometry. Distrust a backward-scroll hint when the first laid-out child sits flush with the container's top edge. It's broader, but RecyclerView only lays out visible rows, so it could hide real content above.

    I lean toward 1. I can open a PR for whichever you prefer.

  2. thymikee commented on Oct 17, 2026

    @thymikee
    Member

    Thanks, the raw-tree root cause is clear. Go with option 1, scoped to the edge verdict. When a scrollable has the same rect as its scrollable ancestor, scroll top and scroll bottom take the edge hints from the ancestor. Leave the snapshot's reported hints unchanged. Add the Settings tree as the regression fixture.

    Option 2 would hide real content above in a RecyclerView. A PR is welcome.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions