Skip to content

[Android] Fix ScrollToAsync UI-thread livelock caused by the MapRequestScrollTo retry loop - #39213

Merged
kubaflo merged 6 commits into
dotnet:inflight/currentfrom
philipag:fix-scrollto-livelock
Oct 8, 2026
Merged

kubaflo merged 6 commits into
dotnet:inflight/currentfrom
philipag:fix-scrollto-livelock

Conversation

@philipag

@philipag philipag commented Oct 7, 2026 •

Copy link
Copy Markdown

The bug

On Android, ScrollView.ScrollToAsync can enter a state where the returned Task never completes and the handler spins the UI thread forever — thousands of looper messages per second, continuous JNI-bridge allocations, GC churn, battery drain — until the process is killed. The trigger is the handler's own wait condition !IsLaidOut || IsLayoutRequested becoming permanently true, which an ordinary app can cause without touching any MAUI internals.

Cause

MapRequestScrollTo defers by re-posting itself to the same looper it is running on:

if (!handler.PlatformView.IsLaidOut || handler.PlatformView.IsLayoutRequested)
{
    handler.PlatformView.Post(() =>
    {
        if (handler.IsConnected())
            MapRequestScrollTo(handler, scrollView, args);
    });
    return;
}

This treats IsLayoutRequested == true as "a traversal is scheduled, so the condition will eventually clear". Android does not guarantee that: ViewRootImpl silently swallows requestLayout() while it is handling a layout-inside-layout request (mHandlingLayoutInLayoutRequest) — routine when a layout callback resizes or scrolls the view itself (parallax, keyboard-adaptive containers, auto-scroll lists). The flag then stays set with no traversal ever coming, so every armed retry re-posts forever: one permanent message-loop item plus one RunnableImplementor/closure allocation per ScrollToAsync call.

A flag-polled retry cannot detect or escape this state, because the state is defined by the flag no longer meaning what the loop assumes it means.

Repro

Self-contained MAUI app: ScrollBug-repro.zip (sources + raw logcat, no build outputs; build with cd repro && dotnet build). It uses the stock ScrollView and no handler overrides; one test-page hook subscribes to the view's ViewTreeObserver.GlobalLayout and calls RequestLayout() from inside the callback — precisely the swallowed-request pattern above. The app then verifies the latch (40 ms sampling of IsLaidOut/IsLayoutRequested, reports STUCK pct=100 laidOutPct=100), fires 20 × ScrollToAsync(0, y, false), and counts its own UI-thread message flow through a Looper printer.

Pixel 8 Pro emulator (API 36), stock Microsoft.Maui.Controls 11.0.0-rc.1.26451.6:

completedInBudget=0/20 — every Task HUNG
idleMsgs=1,213/s with the screen on; 5,325/s backgrounded with the screen off (mWakefulness=Dozing)
census top: ViewRootImpl$ViewRootHandler <- mono.java.lang.RunnableImplementor  x~182,000 per 30 s
CPU 115 % of one core, 4.5 MB/s allocated

The same app with the scroll calls removed reports 1 msg/s — a healthy ScrollView never produces this signature. Raw logcat for every run is in the zip.

The fix

Keep the deferral for the case it exists for (before the first layout), but stop gating it on a flag that can lie forever:

  1. Serve once the view has been laid out at all. Platform ScrollTo clamps against the last completed layout, so serving is never less correct than waiting — and in the latched state it is the only option that terminates. IsLayoutRequested drops out of the branch condition.
  2. Wait on the event whose arrival the wait is for: a one-shot ViewTreeObserver.OnGlobalLayoutListener, after RequestLayout() arms the traversal when none is pending. Event-driven, so nothing polls or self-re-posts.
  3. Bound the wait (4 s): a view that never lays out (e.g. inside a collapsed container) still serves the clamped scroll and completes the caller's Task rather than parking it forever.

The served path is untouched — same pixel conversion, same ScrollFinished() semantics — so requests that work today behave identically.

Results

Same app, same verified-frozen state, A/B by swapping only the RequestScrollTo mapping:

Pixel 8 Pro emulator, 20 calls stock rc-1 this change
Tasks completed 0 / 20 (all hang) 20 / 20 (max 5 ms)
idle UI-thread msgs/s, screen on 1,213 ordinary redraw
idle UI-thread msgs/s, screen off + backgrounded 5,532 0 (0 % CPU)
healthy-view sanity legs OK OK, unchanged

It gets worse off-screen: vsync work disappears and the retry chains run at raw looper throughput. No platform mitigation applies — the loop holds no wakelock, alarm or job for Doze/App Standby to gate, and battery optimization is enabled for the repro package and changes nothing. That combination is why this shows up as battery drain on an idle screen with no console output and no wake-lock in the usage stats.

Related issues

Same wait branch, reported as hangs / silent no-ops:

  • #15387 — Calling ScrollView ScrollToAsync in OnNavigatedTo event or OnAppearing event does not return.
  • #10283 — ScrollView.ScrollToAsync not working if Children.Add used in the same method
  • #19835 — ScrollToAsync does not work on nested ScrollView in Android
  • #14594 — ScrollToAsync Not Working on Andrioid (closed as duplicate)

Those reports show the hang / no-op face of this branch. When the view is in the latched state described above, each deferred request instead becomes a permanent item on the message loop — the CPU/GC/battery half of this bug. Those issues also discuss the completion callback parking inside MauiScrollView, which this PR deliberately does not touch: it fixes the scheduling half, so the latched state terminates instead of livelocking.

Review focus

  1. Is gating the deferral on IsLaidOut alone the right predicate?
  2. Is the shape/value of the bounded fallback (a 4 s PostDelayed) acceptable, or would you prefer it sourced from an existing constant?

Two device tests are now in this PR (src/Core/tests/DeviceTests/Handlers/ScrollView/ScrollViewHandlerTests.Android.cs), covering both ways the wait can end:

  • ScrollToRequestDeferredBeforeFirstLayoutIsServedByThatLayout - laid out later, served by that layout pass.
  • ScrollToRequestForNeverLaidOutViewTerminatesOnceWithoutRetrying - GONE on a live window, served once by the bounded fallback, then nothing.

Against main both fail with Actual: 0 after their full observation window, i.e. the request is never served; against the handler in this PR both pass, and the surrounding suite is unchanged (Core 86/84 passed/2 skipped, Controls 13/13). Measured logs, the message census before and after, and the harness used are in the replies to the two review threads.


Analysis, repro harness and measurements in this PR were produced with AI assistance (qwen3.8-flash-next); every number above was measured on the emulator and is reproducible from the attached zip.

kubaflo and others added 3 commits October 7, 2026 00:08
…#39190)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Description of Change

<!-- Enter description of the fix in this section -->

Regenerate only `issue-triage.lock.yml` using gh-aw v0.86.2 against
deployed main `8c974ad7037c6e171e951883c752bbcea1514cc3`, following
dotnet#39065. Shared `pat_pool.md` frontmatter changed before merge, leaving
the triage lock stale. Actual production runs
[37503999448](https://github.com/dotnet/maui/actions/runs/37503999448)
and
[37503998307](https://github.com/dotnet/maui/actions/runs/37503998307)
failed before inference/publication with `E009 CONFIG_HASH_MISMATCH`.

The compiler changes only `frontmatter_hash` in the first metadata line:
- Previous:
`d2c371bf924eb254532ec722f31d817ebdb42fd4e5565b4952a40e09e465e0d1`
- Regenerated:
`eb116514f337eb247c8767becceaefdd9cfb69da65044098b5cdc510e7c2d153`

The new value matches the actual activation runtime recomputation in run
37503999448. All bytes after that metadata line are unchanged:
authorization, GPT-only inference, permission boundaries, publication,
hash checks, action/container pins and job conditions are preserved. Add
a deployment note requiring dependent lock recompilation after shared
import frontmatter changes. No workflow source, shared import, script or
C# changes.

Configuration validation:
- `git merge-base --is-ancestor 8c974ad
HEAD` — passed before editing; initial HEAD was exactly that deployed
squash merge.
- `gh aw compile issue-triage --strict --no-check-update` — passed; 1
workflow succeeded, 0 warnings, compiler v0.86.2.
- `gh aw hash-frontmatter .github/workflows/issue-triage.md` — passed;
output equals regenerated metadata and actual runtime hash above.
- `test "$(gh aw hash-frontmatter .github/workflows/issue-triage.md)" =
"$(head -n 1 .github/workflows/issue-triage.lock.yml | sed 's/^#
gh-aw-metadata: //' | jq -r .frontmatter_hash)"` — passed.
- `cmp <(git show HEAD:.github/workflows/issue-triage.lock.yml | tail -n
+2) <(tail -n +2 .github/workflows/issue-triage.lock.yml)` — passed
before committing; compiled lock excluding metadata is byte-identical to
deployed main.
- `actionlint -ignore 'unexpected key "queue" for "concurrency" section'
.github/workflows/issue-triage.lock.yml` — passed with actionlint
1.7.12; only the existing narrow compiler-generated queue exception.
- `git diff --check` — passed.

No pipeline/Pester/snapshot/synthetic fixture tests or product builds
were added or run. No production commands, dispatches or issue-label
mutations were performed for this correction. Post-merge production
outcome audit remains separate; local validation does not claim hosted
end-to-end success.

### Issues Fixed

<!-- Please make sure that there is a bug logged for the issue being
fixed. The bug should describe the problem and how to reproduce it. -->

Follow-up to dotnet#39065. No issue is closed: the reproduced defect is the
activation failure in the linked actual production runs, not the
underlying product reports used as smoke-test targets.

<!--
Are you targeting main? All PRs should target the main branch unless
otherwise noted.
-->
…dotnet#39188)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Description of Change

<!-- Enter description of the fix in this section -->

Fix a confirmed startup-deadline regression in the CI-fix Azure DevOps
dispatcher introduced by dotnet#39147, and correct a separate, pre-existing
producer prompt contradiction that blocks mandatory safe-output
registration.

**Production evidence:** [run
37477952193](https://github.com/dotnet/maui/actions/runs/37477952193)
spent approximately 533 seconds between starting the 420-second deadline
and completing trusted checkout; [run
37485076087](https://github.com/dotnet/maui/actions/runs/37485076087)
spent approximately 494 seconds. Both then failed with `The variable
cannot be validated because the value 0 is not a valid value for the
DispatcherBudgetSeconds variable.`

**Root cause:** The script parameter `[ValidateRange(1,
540)][int]$DispatcherBudgetSeconds` and top-level
`$script:DispatcherBudgetSeconds` are the same validated PowerShell
variable. Clamping valid caller input `420` against an already-expired
absolute deadline assigns `0` to that variable, triggering validation
before explicit budget-exhaustion reporting can run. Extracted helper
tests did not exercise this entrypoint initialization.

**Correction:**
- Store the clamped runtime allowance in the distinct
`EffectiveDispatcherBudgetSeconds` variable and update remaining-budget
helpers and their extracted-test state consistently. Caller budget `0`
remains invalid.
- Fail startup explicitly with `[dispatcher-budget-exhausted]` when
preparation has consumed the deadline, before PR discovery,
authentication, HTTP, or sleep. Write a failed preparation/deadline job
summary and state `No queue POST was attempted.` This also fails rather
than claiming successful no-work reconciliation when the deadline is
expired.
- Limit trusted checkout to the standalone
`/.github/scripts/Queue-CiFixAzdoValidation.ps1` using non-cone sparse
checkout. The existing SHA-pinned checkout v7.0.1 supports exact
patterns and automatically uses `blob:none` for sparse fetches. Preserve
`github.sha`, `fetch-depth: 1`, and `persist-credentials: false`; never
fetch or execute PR code.
- Document preparation behavior and limitations. Keep the absolute
pre-checkout 420-second deadline and ten-minute hard job timeout
unchanged; do not restart or extend either allowance.

**Validation:** Baseline dispatcher suite: 73 passed. Before the script
correction, the six new actual-entrypoint cases produced four expected
expired-deadline failures with the original validation exception and two
passing caller-validation/future-deadline cases. After correction, all
**79 tests passed**, including existing helper/queue regressions.
Expired cases assert the preparation summary, explicit prefix, no-POST
message, and zero HTTP/sleep calls; a native PowerShell process asserts
nonzero exit. The valid future deadline still produces the three
verified offline pipeline payloads.

Exact final commands and results:

```powershell
pwsh -NoProfile -Command '$env:PSModulePath = "/Users/shneuvil/.copilot/session-state/237fbb01-6532-4170-9f5e-6af14fa3a5e0/files/powershell-modules" + [IO.Path]::PathSeparator + $env:PSModulePath; Import-Module Pester -ErrorAction Stop; Invoke-Pester -Path .github/scripts/Queue-CiFixAzdoValidation.Tests.ps1 -Output Normal -CI'
# Passed: 79; failed/skipped: 0. Reused existing Pester; no dependency installation.

pwsh -NoProfile -Command '$files = @(".github/scripts/Queue-CiFixAzdoValidation.ps1", ".github/scripts/Queue-CiFixAzdoValidation.Tests.ps1"); foreach ($file in $files) { $tokens = $null; $errors = $null; [void][System.Management.Automation.Language.Parser]::ParseFile((Join-Path $PWD $file), [ref]$tokens, [ref]$errors); if ($errors.Count) { $errors | ForEach-Object { Write-Error $_ }; exit 1 }; Write-Output "Parse passed: $file" }'
# Passed: both PowerShell files parsed without errors.
```

```bash
actionlint .github/workflows/ci-fix-azdo-validation.yml
# Passed.
git diff --check
# Passed.
```

**Limits:** The sparse-checkout assertion is a static
configuration/trust regression, not a hosted checkout timing
measurement. Production checkout performance remains unproven; checkout
or PowerShell startup exceeding the ten-minute hard guard can still
cancel the job before a summary is written. No live Azure queue
requests, manual workflow dispatches/reruns, `/azp` requests, or
credential/permission/model-policy changes were performed. There is
still no successful eligible-PR Azure canary evidence here; this PR does
not establish Azure permissions or exact-head check linkage for any of
the three pipelines.

**Related producer blocker (pre-existing, not introduced by dotnet#39147):**
[main producer run
37464881821](https://github.com/dotnet/maui/actions/runs/37464881821)
and [net11 producer run
37465231994](https://github.com/dotnet/maui/actions/runs/37465231994)
concluded success but emitted `missing_tool` / `report_incomplete`. The
net11 diagnostic explicitly says mandatory expectation registration
requires PowerShell but the run instructions exclude it. The main
diagnostic likewise reports the prompt-specific `no-pwsh` restriction,
alongside separate unavailable cited Azure timeline evidence. This is an
upstream producer blocker: a compliant PR cannot reach dispatcher
validation if required registration is prohibited by its own prompt.

Both producers already allow `pwsh` in `tools.bash`, and Hard Rule 11
requires `Register-CiFixSafeOutputExpectation.ps1` or atomic
validation/registration via `Test-CiFixTransport.ps1`. Their bottom
Environment constraints sections nevertheless said `no pwsh`. The
mirrored correction changes only that prose: explicitly identify `pwsh`
availability for those deterministic helpers, retain `no gh` / `no
python` and `curl` + `jq` API guidance, and reiterate existing
fail-closed registration/transport requirements. No tool allowlists,
network, permissions, credentials, trust, model policy, or output gates
change.

Added two assertions in the existing registration test suite protecting
both producer twins. Both failed on the old contradictory guidance and
pass after correction. Regenerated both locks with retained **gh-aw
v0.86.2**, without manual edits or compiler upgrade; **only `body_hash`
changes**. Frontmatter and all generated lock runtime content remain
identical.

Additional exact validation commands and results:

```powershell
pwsh -NoProfile -Command '$env:PSModulePath = "/Users/shneuvil/.copilot/session-state/237fbb01-6532-4170-9f5e-6af14fa3a5e0/files/powershell-modules" + [IO.Path]::PathSeparator + $env:PSModulePath; Import-Module Pester -ErrorAction Stop; $config = New-PesterConfiguration; $config.Run.Path = @(".github/scripts/Register-CiFixSafeOutputExpectation.Tests.ps1", ".github/scripts/Test-CiFixTransport.Tests.ps1", ".github/scripts/Queue-CiFixAzdoValidation.Tests.ps1"); $config.Run.Exit = $true; $config.Output.Verbosity = "Normal"; $config.TestResult.Enabled = $true; $config.TestResult.OutputPath = "/Users/shneuvil/.copilot/session-state/1e0cc90a-a521-4055-9e7a-9565cdaf5504/files/producer-and-dispatcher-pester-results.xml"; Invoke-Pester -Configuration $config'
# Passed after lock regeneration: 116 tests (6 registration, 31 transport, 79 dispatcher); 0 failed/skipped.

pwsh -NoProfile -Command '$file = Join-Path $PWD ".github/scripts/Register-CiFixSafeOutputExpectation.Tests.ps1"; $tokens = $null; $errors = $null; [void][System.Management.Automation.Language.Parser]::ParseFile($file, [ref]$tokens, [ref]$errors); if ($errors.Count) { $errors | ForEach-Object { Write-Error $_ }; exit 1 }; Write-Output "PowerShell parse passed"'
# Passed.
```

```bash
/Users/shneuvil/.copilot/session-state/237fbb01-6532-4170-9f5e-6af14fa3a5e0/files/tools/gh-aw-0.86.2/darwin-arm64 compile ci-status-fix ci-status-fix-net11 --strict --no-emit --no-check-update
# Passed: 2 workflows, 0 warnings.
/Users/shneuvil/.copilot/session-state/237fbb01-6532-4170-9f5e-6af14fa3a5e0/files/tools/gh-aw-0.86.2/darwin-arm64 compile ci-status-fix ci-status-fix-net11 --strict --no-check-update
# Passed: 2 workflows, 0 warnings; emitted body-hash-only changes.
actionlint .github/workflows/ci-status-fix.lock.yml .github/workflows/ci-status-fix-net11.lock.yml
# Nonzero both before and after regeneration: identical six baseline diagnostics.
# Each twin: concurrency.queue not recognized, ShellCheck SC2129 style, github.aw expression not recognized.
# No new lint findings; baseline/after outputs compared byte-identical.
```

**Producer limits:** These are static prompt/configuration assertions
and offline helper tests, not a hosted agent behavior evaluation.
Removing the contradiction does not guarantee that an agent will
register correctly, find a viable product fix, create a PR, or attach
Azure checks. Separate cited-timeline failures and other producer
blockers are not fixed here. No live workflow or network validation
tests were run.

### Issues Fixed

<!-- Please make sure that there is a bug logged for the issue being
fixed. The bug should describe the problem and how to reproduce it. -->

Follow-up to dotnet#39147 for the production startup regression evidenced by
the two linked dispatcher runs, plus the separate pre-existing producer
helper-guidance contradiction evidenced by the two producer runs. No
separate tracking issue is closed by this PR.

<!--
Are you targeting main? All PRs should target the main branch unless
otherwise noted.
-->

---------

Co-authored-by: PureWeen <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1e0cc90a-a521-4055-9e7a-9565cdaf5504
Copilot AI balanced review requested due to automatic review settings October 7, 2026 08:08
@philipag
philipag deployed to copilot-pat-pool October 7, 2026 08:08 — with GitHub Actions Active
@github-actions

github-actions Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 39213

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 39213"

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service dotnet-policy-service Bot added the community ✨ Community Contribution label Oct 7, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Hey there @@philipag! Thank you so much for your PR! Someone from the team will get assigned to your PR shortly and we'll get it reviewed.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Hey there @philipag! Thank you so much for your PR! Someone from the team will get assigned to your PR shortly and we'll get it reviewed.

@philipag
philipag deployed to copilot-pat-pool October 7, 2026 08:08 — with GitHub Actions Active
@philipag

philipag commented Oct 7, 2026

Copy link
Copy Markdown
Author

@dotnet-policy-service agree

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

The timeout recursively re-arms instead of completing requests for views that never lay out.

Review effort: Balanced
Findings: 1 High severity · 1 Low severity

Open (2)
What changed in this PR

Fixes an Android ScrollToAsync UI-thread livelock caused by repeatedly polling layout state.

Changes:

  • Defers only until the first layout.
  • Uses a one-shot global-layout listener with a four-second timeout.
  • Removes IsLayoutRequested from the serving condition.
File Description
ScrollViewHandler.Android.cs Reworks deferred scroll scheduling and fallback behavior.

Comment thread src/Core/src/Handlers/ScrollView/ScrollViewHandler.Android.cs Outdated
Comment thread src/Core/src/Handlers/ScrollView/ScrollViewHandler.Android.cs Outdated
@kubaflo

This comment has been minimized.

@kubaflo

kubaflo commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

/azp run

@github-actions github-actions Bot added the s/agent-review-in-progress AI review is currently running for this PR label Oct 7, 2026
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).

@github-actions

github-actions Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Note

🔍 /review started

AzDO build 15596260 is running for android.

The s/agent-review-in-progress label stays on this PR while the run is active. The final recommendation and outcome labels are posted only after Gate, expert review, and Deep UI tests finish.

@kubaflo kubaflo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please check the ai's suggestions and add test?

@MauiBot

This comment has been minimized.

@MauiBot MauiBot added s/agent-review-incomplete AI review did not complete all expected phases s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review) and removed s/agent-review-in-progress AI review is currently running for this PR labels Oct 7, 2026
@github-actions github-actions Bot added the s/agent-review-in-progress AI review is currently running for this PR label Oct 7, 2026
@github-actions

github-actions Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Note

🔍 /review started

AzDO build 15596748 is running for android.

The s/agent-review-in-progress label stays on this PR while the run is active. The final recommendation and outcome labels are posted only after Gate, expert review, and Deep UI tests finish.

@MauiBot

This comment has been minimized.

@MauiBot MauiBot removed the s/agent-review-in-progress AI review is currently running for this PR label Oct 7, 2026
@github-actions github-actions Bot added the s/agent-review-in-progress AI review is currently running for this PR label Oct 7, 2026
@philipag
philipag force-pushed the fix-scrollto-livelock branch from 087efcf to d0dbb69 Compare October 8, 2026 06:21
@philipag
philipag force-pushed the fix-scrollto-livelock branch from d0dbb69 to ad2951c Compare October 8, 2026 06:27
@philipag

philipag commented Oct 8, 2026 •

Copy link
Copy Markdown
Author

Thanks - both points from the review are addressed, and the two inline threads are resolved.

The AI suggestions

  1. Timeout is not terminal - fixed. Resume() and OnTimeout call ServeScrollTo directly, so neither re-enters MapRequestScrollTo and the re-arming loop is gone by construction rather than by a re-check of the latch. The 4,000 ms LayoutWaitTimeoutMillis is kept on purpose: removing it would mean silently dropping a restore that arrives before the first layout. Measured before/after, including the message census, is in that thread's reply.
  2. Add coverage - added to src/Core/tests/DeviceTests/Handlers/ScrollView/ScrollViewHandlerTests.Android.cs: ScrollToRequestDeferredBeforeFirstLayoutIsServedByThatLayout and ScrollToRequestForNeverLaidOutViewTerminatesOnceWithoutRetrying. Against main both fail (Expected: 1 / Actual: 0 - the request is never served) and nothing else does: 86 total, 82 passed, 2 failed, 2 skipped. With this handler both pass and the suite returns to 84 passed / 0 failed; Controls.DeviceTests is 13/13.

Head is ad2951c2 - two commits: handler +63/-44, tests +217/-0. I rewrote the branch twice since the first push, each time asserting the rewritten commit's tree hash equals the tree already published and that git diff against the remote is empty, so content did not change; the second rewrite only shortened the two commit headlines, which GitHub was rendering truncated at 72 columns. (The first removed three contentless commits a flawed skip-guard of mine had created - my error, disclosure over cleanup.)

One thing a maintainer needs to do: queue-validation is red on every head since 06:20 UTC, including this one, and it is not this PR's failure:

So nothing in this PR can move that check; re-running the shared workflow, or the CI-fix queue draining, clears it. @kubaflo - both asks in your review are addressed above (AI findings answered in their threads, tests added), so a re-review when convenient would settle the review state; I cannot request one myself (the reviewers endpoint answers 404 for an outside collaborator).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The new predicate can lose scroll requests during legitimate pending layouts, and waiter cleanup is missing during handler disconnection.

3 open findings
2 resolved since last review

🧠 Review effort: Balanced


Give feedback about Copilot approvals in this survey to enter a drawing for a $150 gift card.

// A view that has been laid out at least once has geometry worth scrolling against: the
// platform clamps the offsets to the last completed layout. IsLayoutRequested must not gate
// this decision - see WaitToScrollOnLayout for why waiting on it never ends.
if (!handler.PlatformView.IsLaidOut)
{
var platformView = handler.PlatformView;
var observer = platformView.ViewTreeObserver;
var waiter = new LayoutWaiter(platformView, handler, request);
Comment on lines +403 to +404
var servedAt = DateTime.UtcNow;
var deadline = servedAt.AddSeconds(6);
Copilot AI balanced review requested due to automatic review settings October 8, 2026 06:29
@kubaflo

This comment has been minimized.

@kubaflo

kubaflo commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

/azp run

@github-actions github-actions Bot added the s/agent-review-in-progress AI review is currently running for this PR label Oct 8, 2026
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).

@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

Note

🔍 /review started

AzDO build 15610265 is running for android.

The s/agent-review-in-progress label stays on this PR while the run is active. The final recommendation and outcome labels are posted only after Gate, expert review, and Deep UI tests finish.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Detached views bypass the effective timeout, and stale waiters can target a reused handler.

4 open findings

🧠 Review effort: Balanced


Give feedback about Copilot approvals in this survey to enter a drawing for a $150 gift card.

observer.AddOnGlobalLayoutListener(waiter);
}

platformView.PostDelayed(waiter.OnTimeout, LayoutWaitTimeoutMillis);

@MauiBot MauiBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expert Review — 3 findings

See inline comments for details.

// A view that has been laid out at least once has geometry worth scrolling against: the
// platform clamps the offsets to the last completed layout. IsLayoutRequested must not gate
// this decision - see WaitToScrollOnLayout for why waiting on it never ends.
if (!handler.PlatformView.IsLaidOut)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 AI-Generated Review (multi-model)

[major] Logic and Correctness — Removing IsLayoutRequested from the serve predicate can drop valid add-then-scroll requests: when IsLaidOut is true but a content-growth layout is pending, ServeScrollTo clamps against stale pre-layout extent and no replay occurs after the new layout, so the requested offset is under-shot (repro shape in #10283).

observer.AddOnGlobalLayoutListener(waiter);
}

platformView.PostDelayed(waiter.OnTimeout, LayoutWaitTimeoutMillis);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 AI-Generated Review (multi-model)

[major] Async and Threading Safety — platformView.PostDelayed(waiter.OnTimeout, ...) is not a reliable wall-clock timeout for never-attached/detached views; the runnable can remain queued until attach and never fire, leaving ScrollToAsync incomplete indefinitely on that lifecycle path.

observer.RemoveOnGlobalLayoutListener(this);
}

if (_handler.IsConnected())

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 AI-Generated Review (multi-model)

[major] Handler Mapper and Property Patterns — Deferred request ownership is not cleaned up on disconnect: Fire() drops the serve when !_handler.IsConnected() and there is no handler-level pending-request completion path, so a disconnect/detach before layout/timeout can orphan the corresponding ScrollToAsync task.

@MauiBot MauiBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review Summary

@philipag — new AI review results are available based on commit ad2951c.

Gate Passed Confidence Low Platform Android


🗂️ Review Sessions — click to expand
🚦 Gate — Test Before & After Fix

Gate Result: ✅ PASSED

Platform: ANDROID · Base: main · Merge base: b926f057

Test Without Fix (expect FAIL) With Fix (expect PASS)
📱 ScrollViewHandlerTests (ScrollToRequestDeferredBeforeFirstLayoutIsServedByThatLayout, ScrollToRequestForNeverLaidOutViewTerminatesOnceWithoutRetrying) Category=ScrollView ✅ FAIL — 514s ✅ PASS — 390s
🔴 Without fix — 📱 ScrollViewHandlerTests (ScrollToRequestDeferredBeforeFirstLayoutIsServedByThatLayout, ScrollToRequestForNeverLaidOutViewTerminatesOnceWithoutRetrying): FAIL ✅ · 514s

(no coded error found; showing last 1200 chars)

"totalMemoryBytes": 8322998272
          },
          "target": {
            "kind": "emulator",
            "name": "sdk_gphone_x86_64",
            "identifier": "emulator-5554",
            "operatingSystem": "Android",
            "operatingSystemVersion": "11",
            "apiLevel": 30,
            "architecture": "x86_64",
            "supportedArchitectures": [
              "x86_64",
              "x86",
              "arm64-v8a",
              "armeabi-v7a",
              "armeabi"
            ],
            "manufacturer": "Google",
            "model": "sdk_gphone_x86_64",
            "productName": "sdk_gphone_x86_64",
            "buildFingerprint": "google/sdk_gphone_x86_64/generic_x86_64_arm64:11/RSR1.201211.001/7027799:user/release-keys",
            "cpuModel": "0",
            "cpuMaxFrequencyHertz": 2000,
            "totalMemoryBytes": 2077544448
          }
        }
      }
      <<XHARNESS_RESULT_END>>
info: Attempting to remove apk 'com.microsoft.maui.core.devicetests'..
info: Successfully uninstalled com.microsoft.maui.core.devicetests
XHarness exit code: 1 (TESTS_FAILED)
  Passed: 0
  Failed: 2
  Skipped: 0
  Total: 2
  Tests completed with exit code: 1
🟢 With fix — 📱 ScrollViewHandlerTests (ScrollToRequestDeferredBeforeFirstLayoutIsServedByThatLayout, ScrollToRequestForNeverLaidOutViewTerminatesOnceWithoutRetrying): PASS ✅ · 390s

(no coded error found; showing last 1200 chars)

795000,
            "totalMemoryBytes": 8322998272
          },
          "target": {
            "kind": "emulator",
            "name": "sdk_gphone_x86_64",
            "identifier": "emulator-5554",
            "operatingSystem": "Android",
            "operatingSystemVersion": "11",
            "apiLevel": 30,
            "architecture": "x86_64",
            "supportedArchitectures": [
              "x86_64",
              "x86",
              "arm64-v8a",
              "armeabi-v7a",
              "armeabi"
            ],
            "manufacturer": "Google",
            "model": "sdk_gphone_x86_64",
            "productName": "sdk_gphone_x86_64",
            "buildFingerprint": "google/sdk_gphone_x86_64/generic_x86_64_arm64:11/RSR1.201211.001/7027799:user/release-keys",
            "cpuModel": "0",
            "cpuMaxFrequencyHertz": 2000,
            "totalMemoryBytes": 2077544448
          }
        }
      }
      <<XHARNESS_RESULT_END>>
info: Attempting to remove apk 'com.microsoft.maui.core.devicetests'..
info: Successfully uninstalled com.microsoft.maui.core.devicetests
XHarness exit code: 0
  Passed: 2
  Failed: 0
  Skipped: 0
  Total: 2
  Tests completed successfully
📁 Fix files reverted (1 files)
  • src/Core/src/Handlers/ScrollView/ScrollViewHandler.Android.cs

📋 Pre-Flight — Context & Validation

Pre-Flight Context

PR snapshot

  • PR: #39213 — [Android] Fix ScrollToAsync UI-thread livelock caused by the MapRequestScrollTo retry loop
  • State: Open, non-draft; GitHub reports mergeability as blocked.
  • Submitted revision: ad2951c24a68be84ae7bf959afb3c06ec25aa23f
  • Base revision: b926f05713b0d1843f8f3bef28b025c6242f1d3c (main)
  • Scope: 2 files, 325 additions, 6 deletions.
  • The immutable diff supplied in the request matches the two files GitHub reports. This context does not assess later worktree state.

Reported problem and relevant existing behavior

The PR reports an Android-specific ScrollView.ScrollToAsync livelock. At the base revision, ScrollViewHandler.MapRequestScrollTo defers while either PlatformView.IsLaidOut is false or PlatformView.IsLayoutRequested is true. Deferral uses PlatformView.Post to invoke the same mapper again.

The reported failure mode is that Android can leave IsLayoutRequested true without scheduling another traversal when RequestLayout() occurs during layout. The reposted mapper then continually sees the same flag and reposts itself, allegedly causing an incomplete ScrollToAsync task, sustained UI-looper traffic, allocations, and CPU/battery use.

Surrounding source supports the completion dependency:

  • ScrollView.ScrollToAsync creates a TaskCompletionSource and returns its task.
  • The task completes when IScrollView.ScrollFinished() reaches SendScrollFinished().
  • Android’s MauiScrollView.ScrollTo calls the completion callback immediately after an instant native scroll, or when its one-second ValueAnimator ends for an animated scroll.
  • The handler calls VirtualView.ScrollFinished() only while connected. A request that is deferred indefinitely or dropped before that callback can therefore leave the public task incomplete.

The external reproduction archive, raw logs, Android framework internals, and performance measurements cited in the PR description were not independently inspected. Their contents and numerical claims remain unverified context.

Changed files and mechanism

  1. src/Core/src/Handlers/ScrollView/ScrollViewHandler.Android.cs

    • Removes IsLayoutRequested from the initial defer predicate. A view that has completed any layout is now served immediately against its current geometry.
    • Extracts the existing conversion, platform scroll, and completion callback into ServeScrollTo.
    • For a view not yet laid out, adds WaitToScrollOnLayout:
      • Registers a ViewTreeObserver.IOnGlobalLayoutListener when the observer is alive.
      • Schedules a four-second fallback with platformView.PostDelayed.
      • Calls RequestLayout() only when IsLayoutRequested is false.
    • Adds LayoutWaiter, which uses Interlocked.Exchange as a one-shot guard shared by the layout and timeout paths.
    • On firing, removes itself from the current live ViewTreeObserver and calls ServeScrollTo only if the handler remains connected.
    • The timeout path goes directly to ServeScrollTo; it does not re-enter the layout predicate.
  2. src/Core/tests/DeviceTests/Handlers/ScrollView/ScrollViewHandlerTests.Android.cs

    • Adds a four-second test-side bound corresponding to the private production constant.
    • Adds ScrollToRequestDeferredBeforeFirstLayoutIsServedByThatLayout:
      • Starts with an attached but GONE, never-laid-out view.
      • Makes it visible and induces a layout-requested latch from a global-layout callback.
      • Expects the deferred request to be served once before the timeout.
      • Expects a later request in IsLaidOut && IsLayoutRequested to be served immediately.
      • Waits beyond the fallback period to check that the original request is not served twice.
    • Adds ScrollToRequestForNeverLaidOutViewTerminatesOnceWithoutRetrying:
      • Uses an attached GONE view that remains unlaid-out.
      • Expects no immediate completion, one completion around the fallback bound, and no second completion after another timeout period.
    • Adds a counting IScrollView stub using Volatile.Read and Interlocked.Increment.

Platform-sensitive paths

The implementation and tests are Android-only. Important Android-specific behavior includes:

  • View.IsLaidOut and View.IsLayoutRequested lifecycle semantics.
  • ViewTreeObserver.OnGlobalLayout, which is tree-wide; the waiter filters events by checking whether its own view is laid out.
  • View.PostDelayed behavior while attached versus detached.
  • RequestLayout() behavior during a traversal.
  • Native offset clamping against current child extent.
  • Handler disconnection, platform-view detachment/reattachment, and possible handler reuse.
  • Instant and animated scrolling have different completion paths in MauiScrollView; the added tests invoke ScrollToRequest(..., instant: true) only.

For comparison, the base iOS and Windows handlers explicitly store pending scroll requests as handler state and complete/remove them during DisconnectHandler. The submitted Android waiter is local to each request and is not owned or canceled by DisconnectHandler.

Related issue context

The PR links symptom-adjacent reports, but the available records do not establish that they share this precise root cause:

  • #15387: open; ScrollToAsync called from navigation/appearing does not return, labeled for Android and iOS. The issue body available through GitHub is limited. It references closed #16619.
  • #10283: open; adding children and scrolling in the same method fails. The report says Android completes without scrolling, while Windows remains incomplete until manual scrolling.
  • #19835: open; nested Android ScrollView request is a silent no-op.
  • #14594: closed as a duplicate; Android scroll-to-element/coordinate no-op.

Current Controls source also has separate logic that parks element-target requests until element geometry exists. How that newer Controls-level behavior interacts with each historical issue is unknown.

Validation requirements and supplied evidence

  • Required platform: Android.
  • Provided Gate result: Gate ✅ PASSED — tests FAIL without fix, PASS with fix.
  • The Gate was not re-run.
  • The PR author reports Pixel 8 Pro/API 36 device-test and reproduction measurements, including red/green results and surrounding ScrollView suites. Those figures were not independently verified here.
  • GitHub’s aggregate commit-status endpoint currently reports pending with no legacy status entries. GitHub exposed a large check-run set, but it was not exhaustively inspected; overall CI health is therefore unknown, not absent.

Later Android validation should distinguish at least:

  • First-layout event completion from timeout completion.
  • Attached-but-never-laid-out and never-attached views.
  • No repeated posting, duplicate serving, or retained listener after each outcome.
  • Disconnect, detach/reattach, and handler/platform-view reuse while waiting.
  • Content growth followed immediately by ScrollToAsync, including final offset after the pending layout.
  • Instant and animated requests.
  • Vertical, horizontal, and bidirectional orientations where relevant.
  • Multiple or superseding requests and correct task completion ownership.

Remaining uncertainties

These are unresolved context items, not conclusions:

  • An unresolved review thread argues that dropping IsLayoutRequested may serve against stale geometry during a legitimate pending layout, allowing Android to clamp to the old extent and lose an add-then-scroll request such as #10283.
  • Another thread notes that LayoutWaiter.Fire does not complete the request when the handler has disconnected. Because the request has already left Controls’ pending path, the public task may remain incomplete; lifecycle ownership and cleanup need confirmation.
  • A further thread claims View.PostDelayed on a detached or never-attached view may not reach the main looper until attachment, defeating the wall-clock timeout. The added timeout test covers an attached GONE view, not a never-attached view.
  • The first test records its event-path timing after the request and layout trigger. An unresolved comment argues that elapsed setup time could allow the fallback to fire while the measured interval still appears shorter than four seconds.
  • Removing the listener through _view.ViewTreeObserver assumes the observer used for removal corresponds appropriately to the observer used for registration across detach/reattach.
  • When layout wins, the delayed callback remains scheduled until its four-second deadline; the one-shot guard prevents a second serve, but temporary object retention and callback cleanup were not characterized.
  • The four-second timeout value and user-visible latency are policy choices with no cited shared constant.
  • Serving a never-laid-out view completes the task but may apply no offset because the platform has zero scrollable geometry. The PR author explicitly reports that callers needing the offset after a later GONE → VISIBLE transition would need another request.
  • The PR currently has unresolved automated review threads and an earlier human changes-requested review asking that automated suggestions be checked and tests added. Tests were subsequently added, but no later human disposition was observed.
  • This is context gathering only, not an exhaustive code review or an adjudication of the submitted approach.

🔬 Code Review — Deep Analysis

Expert PR Evaluation

Verdict: NEEDS_CHANGES
Confidence: low

Independent Assessment

The submitted Android change replaces recursive looper polling before first layout with a one-shot global-layout listener plus a four-second fallback, while serving requests immediately after any completed layout. The one-shot guard prevents the event and timeout paths from serving the same request twice, but the submitted implementation does not preserve request ownership across all layout and handler lifecycle states.

Blocking Findings

  1. ❌ Stale geometry can under-serve requests during a legitimate pending layout — src/Core/src/Handlers/ScrollView/ScrollViewHandler.Android.cs:175

    Dropping IsLayoutRequested from the defer predicate means an already-laid-out view with pending content growth scrolls against its previous extent. Android can clamp the request before the new extent exists, and the submitted code has no post-layout replay. This leaves the add-content-then-scroll mechanism unresolved.

  2. ❌ The fallback is not a wall-clock timeout for an unattached or detached view — src/Core/src/Handlers/ScrollView/ScrollViewHandler.Android.cs:236

    View.PostDelayed uses the view callback queue when the view is not attached. The runnable may not reach the main looper until attachment, so a view that never attaches can still leave ScrollToAsync incomplete indefinitely.

  3. ❌ Disconnect can orphan a deferred request — src/Core/src/Handlers/ScrollView/ScrollViewHandler.Android.cs:290

    LayoutWaiter.Fire suppresses serving after IsConnected() becomes false, but the handler does not own, cancel, or complete the pending request during disconnect. A disconnect before layout or timeout therefore has no completion handoff for the public task.

Prior Review Reconciliation

The expert reviewer checked top-level reviews, inline review comments, and PR issue comments. Existing concerns corresponding to all three findings remain applicable to submitted revision ad2951c24a68be84ae7bf959afb3c06ec25aa23f.

Blast Radius and Failure Modes

  • The mapper runs for every Android ScrollToAsync request; the change is platform-specific but affects normal, pre-layout, and lifecycle-transition paths.
  • No new process-wide or static state is introduced.
  • Listener one-shot state prevents duplicate serving, but does not solve request ownership after disconnect.
  • Multiple requests create independent waiters with no supersession or cancellation policy.
  • Added tests exercise attached views and instant scrolling; they do not establish never-attached timeout behavior, disconnect completion, animated completion, or correct final offset after pending content growth.

Evidence Status

  • Gate: ✅ PASSED — the supplied tests fail without the submitted fix and pass with it.
  • Required-check state was not established as clean; the expert observed failing and in-progress check runs through anonymous submitted-SHA evidence after authenticated required-check retrieval was unavailable.
  • Inline findings were persisted to inline-findings.json before refinement consideration.

The three concrete mechanisms meet the evidence-first threshold for one consolidated pr-plus-reviewer refinement.


🛠️ Try-Fix — Analysis & Comparison

Alternative generation: not requested

Evidence-first mode intentionally omits routine Try-Fix attempts. This is not a failed
attempt, a completed alternative search, or evidence that alternatives have no value.
The expert review may still produce one evidence-backed PR refinement.


🏁 Report — Final Recommendation

⚠️ Final Recommendation: REQUEST CHANGES

Phase Status

Phase Status Notes
Pre-Flight ✅ COMPLETE Trusted evidence-first context consumed
Code Review NEEDS_CHANGES (low) 3 blocking errors, 0 warnings
Gate ✅ PASSED Android; tests fail without the submitted fix and pass with it
Try-Fix not requested Evidence-first mode intentionally omitted routine alternatives
Refinement ❌ FAILED One implemented candidate; isolated validation was invalid and its focused build found a compile error
Report ✅ COMPLETE

Code Review and Candidate Comparison

Rank Candidate Evidence Blocking status
1 pr Gate passed for the submitted Android fix Expert review found stale-geometry, unattached-timeout, and disconnect-completion defects
2 pr-plus-reviewer Implemented one consolidated refinement Candidate-local SDK provisioning failed, the subsequent raw-worktree SDK fallback is inadmissible, and the focused candidate build failed with CS0234 at ScrollViewHandler.Android.cs:262

pr wins the bounded comparison because it is the only candidate with valid passing regression evidence. That does not make it merge-ready: the expert findings independently veto approval. The implemented refinement attempted to wait for legitimate pending layouts, schedule the timeout on the main looper, and make disconnect own completion, but a candidate that does not compile and was not validated in isolation must rank below the Gate-passing submitted fix.

Summary

The submitted fix removes the UI-thread repost loop and the supplied Gate demonstrates the targeted regression, but it leaves three blocking lifecycle/layout mechanisms unresolved. Routine Try-Fix was not requested. The sole evidence-backed refinement was implemented once and validated once as required; provisioning broke isolation and the build exposed a compile error, so it cannot replace or validate the PR.

Root Cause

The original Android mapper treats IsLayoutRequested as proof that another traversal will arrive and recursively reposts itself while that flag remains true. Android can leave the flag latched without scheduling a traversal, producing an unbounded looper retry and preventing ScrollToAsync completion.

Fix Quality

The raw PR replaces polling with a one-shot layout listener and fallback, which addresses the observed livelock and passed the supplied Gate. However, serving immediately whenever IsLaidOut is true can clamp against stale extent during legitimate pending content growth; View.PostDelayed does not provide a wall-clock bound before attachment; and a disconnect can remove the only path that completes a deferred request. These are concrete blocking findings, so the submitted fix requires further changes even though it remains the comparison winner.


📱 UI Tests — ViewBaseTests

Detected UI test categories: ViewBaseTests

✅ Deep UI tests — 118 passed, 0 failed, 1 skipped across 1 category on platform-pool agent (replaces in-process counts above).

🧪 UI Test Execution Results (deep, platform pool)

Category Tests Snapshot diffs
ViewBaseTests 118/119 (1 skipped) ✓ —
📎 Download drop-deep-uitests artifact (TRX + snapshot diffs)

🧭 Next Steps — review latest findings

No alternative fix was selected for this run. Review the session findings and CI results before merging.

@MauiBot MauiBot added s/agent-changes-requested AI agent recommends changes - found a better alternative or issues s/agent-gate-passed AI verified tests catch the bug (fail without fix, pass with fix) and removed s/agent-review-incomplete AI review did not complete all expected phases s/agent-review-in-progress AI review is currently running for this PR labels Oct 8, 2026

@kubaflo kubaflo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you check the ai's suggestions?

@kubaflo

This comment has been minimized.

@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

Tests Failure Analysis

@philipag — test-failure analysis for commit ad2951c.

Scope CI failures Commit ad2951c


🧪 CI Analysis — click to expand
📊 maui-pr
  • 🟢 Likely unrelated - Windows Debug MSB4024: targets file locked by another process; changed Android runtime code cannot execute during imports.
  • 🟡 Needs human investigation - RunOnAndroid: maui-blazor/net10.0/Debug: 1,800s Install timeout; target timeout differs in template/framework. Inspect Install binlog.

🧪 maui-pr-devicetests

🧪 maui-pr-uitests

🧭 Follow-up — actions and refresh

Next action: Complete UI/device evidence; inspect Install binlog and unresolved assertions.

Maintainers: comment /review tests to refresh this report.

@kubaflo
kubaflo changed the base branch from main to inflight/current October 8, 2026 17:35
@kubaflo
kubaflo merged commit f3c581a into dotnet:inflight/current Oct 8, 2026
183 of 201 checks passed
@github-actions github-actions Bot added this to the .NET 10 SR12 milestone Oct 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

community ✨ Community Contribution s/agent-changes-requested AI agent recommends changes - found a better alternative or issues s/agent-gate-passed AI verified tests catch the bug (fail without fix, pass with fix) s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants