chore: stabilize Maestro CI (Maestro 2.5.1, emulator headroom)#7311
chore: stabilize Maestro CI (Maestro 2.5.1, emulator headroom)#7311diegolmello wants to merge 27 commits into
Conversation
Maestro 2.5.0 switched gRPC to dadb direct ADB socket — removes the broken-pipe install class on the Android emulator. Drop the monkey warmup that was stressing system_server right before driver install. Broaden iOS retry to fire on any error, not just timeout.
Bump emulator RAM 4G→6G to keep system_server out of the meminfo-dump timeout window, free ~6 GB pre-launch via jlumbroso/free-disk-space, and split the default-profile path into a cache-miss snapshot warmup plus a cache-hit replay (-no-snapshot-save) so we stop cold-booting the AVD on every shard.
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughThis PR upgrades Maestro to 2.5.1 (Android/iOS), changes Android AVD caching and emulator RAM/options, replaces the Android monkey pre-test step with ChangesMaestro Test Infrastructure Upgrade
Sidebar View Layout Change
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Suggested labels
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Warning Review ran into problems🔥 ProblemsErrors were encountered while retrieving linked issues. Errors (4)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
.github/workflows/maestro-android.yml (1)
41-48:⚠️ Potential issue | 🟠 Major | ⚡ Quick winShard 14 can poison the shared AVD cache, defeating the snapshot optimization for all other shards
The
Cache Android AVDstep has noifcondition, so shard 14 participates in both cache restore and save under the same key (avd-${{ runner.os }}-api34).On a cold run, all 14 shards race to save to that key (GitHub Actions is first-writer-wins). If shard 14 wins, the cache stores only
Pixel_API_34_Keyboard.avd+Pixel_API_34_Keyboard.ini. On subsequent runs:
- Non-keyboard shards see
steps.avd-cache.outputs.cache-hit == 'true'- The "Generate AVD snapshot" step is skipped (its condition checks
cache-hit != 'true')- The test step runs with
force-avd-creation: false— but the expected default-profile AVD is absent from the restored cacheThe behavior of
reactivecircus/android-emulator-runnerwhenforce-avd-creation: falseand the named AVD doesn't exist is unspecified; it may silently cold-boot (losing the optimization) or fail outright. Either way, the caching strategy is silently broken.🐛 Proposed fix — exclude shard 14 from AVD caching
- name: Cache Android AVD + if: ${{ inputs.shard != '14' }} id: avd-cache uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: | ~/.android/avd/* ~/.android/adb* key: avd-${{ runner.os }}-api34🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/maestro-android.yml around lines 41 - 48, The "Cache Android AVD" step (id: avd-cache, name: Cache Android AVD, key: avd-${{ runner.os }}-api34) must be excluded from shard 14 so it doesn't poison the shared AVD cache; update the step to run only when the shard is not 14 (e.g. add an if condition referencing your matrix/shard variable such as if: matrix.shard != 14) so shard 14 neither restores nor saves to the common key, leaving other shards to generate and cache the full AVD snapshot correctly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/maestro-android.yml:
- Around line 24-33: The workflow currently uses the jlumbroso/free-disk-space
action with swap-storage: true while configuring the emulator's ram-size: 6144M,
which disables swap and leaves under ~800MB host memory — change either the
swap-storage or ram-size to avoid OOMs: set swap-storage: false to retain the
runner swapfile, or reduce ram-size to 5120M or 4608M so host+emulator fit in
physical RAM; update the keys swap-storage and ram-size in the action usage
(uses: jlumbroso/free-disk-space@...) and apply the same change to the other
occurrences noted (the other blocks referenced) so all workflow entries are
consistent.
In @.github/workflows/maestro-ios.yml:
- Around line 14-15: The comment incorrectly mentions the Android-specific
"dadb"/ADB change; update the comment text that currently references "dadb" and
ADB to an iOS-appropriate note explaining that iOS Maestro uses
idb/libimobiledevice (not ADB) and that the Android-specific CLI 2.5.0 change
does not apply to the iOS workflow; either remove the ADB-specific sentence or
replace it with a short iOS-focused explanation and keep or adjust the release
link as needed so the comment accurately reflects iOS tooling.
---
Outside diff comments:
In @.github/workflows/maestro-android.yml:
- Around line 41-48: The "Cache Android AVD" step (id: avd-cache, name: Cache
Android AVD, key: avd-${{ runner.os }}-api34) must be excluded from shard 14 so
it doesn't poison the shared AVD cache; update the step to run only when the
shard is not 14 (e.g. add an if condition referencing your matrix/shard variable
such as if: matrix.shard != 14) so shard 14 neither restores nor saves to the
common key, leaving other shards to generate and cache the full AVD snapshot
correctly.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: ee299d9c-ce31-45fd-89b1-673983985558
📒 Files selected for processing (3)
.github/scripts/run-maestro.sh.github/workflows/maestro-android.yml.github/workflows/maestro-ios.yml
💤 Files with no reviewable changes (1)
- .github/scripts/run-maestro.sh
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: E2E Build Android / android-build
- GitHub Check: ESLint and Test / run-eslint-and-test
|
Android Build Available Rocket.Chat 4.72.0.108814 |
|
Android Build Available Rocket.Chat 4.72.0.108814 Internal App Sharing: https://play.google.com/apps/test/RQQ8k09hlnQ/ahAO29uNRwjAsJvaiquGjKRmucVItIwkqzqmtF75VX3MLDxOhuznxMA2GxetWxp_dAfRbh33wL0-dHZMTeQVHqchi7 |
|
Android Build Available Rocket.Chat Experimental 4.72.0.108813 |
|
Android Build Available Rocket.Chat Experimental 4.72.0.108813 Internal App Sharing: https://play.google.com/apps/test/RQVpXLytHNc/ahAO29uNTowuRjjHlDYUgXqroMl8epJWmBJOAe-7N_6W5JjAcJyBHu-MfbZDIilMfFnPJkwzs7kKMzme1NVFTUUxnE |
|
iOS Build Available Rocket.Chat Experimental 4.72.0.108816 |
|
Android Build Available Rocket.Chat Experimental 4.72.0.108821 Internal App Sharing: https://play.google.com/apps/test/RQVpXLytHNc/ahAO29uNQLTMEQOTK7Yu4n98ufM0ImDLGPoEI7tdqqt4_8PqA3XXsk_0JhIS1tJIiRPRZJU0Eh3mtvcPjDUgnnZ0rq |
|
Android Build Available Rocket.Chat 4.72.0.108820 Internal App Sharing: https://play.google.com/apps/test/RQQ8k09hlnQ/ahAO29uNQrTqib34jYoZve6A-78lxC3zI5hjF5NuHRtFdGLOxp7tcQ3xwQyvjeTqlI5Lq2c3nCQqQiv5zaR2Rc0VBd |
|
iOS Build Available Rocket.Chat Experimental 4.72.0.108822 |
The iOS / Android Maestro workflows only uploaded *.png under ~/.maestro/tests. JUnit XML (maestro-report.xml, maestro-rerun-round-*.xml) and Maestro's per-flow command logs were discarded, so a CI failure surfaced only as a screenshot of the final state, not the failing step or step trace. Expand the upload-artifact globs on both workflows to include the JUnit reports (in CWD) and the full ~/.maestro/tests tree.
|
Android Build Available Rocket.Chat Experimental 4.73.0.108897 Internal App Sharing: https://play.google.com/apps/test/RQVpXLytHNc/ahAO29uNQyVMMK24jWLqKjDSQbDcUPeEB59eFY-bwI026Z4GFx0R-mxiTEbW8_vyD9bH1qpzhKxu4IDbvmZrAmznqQ |
|
iOS Build Available Rocket.Chat Experimental 4.73.0.108898 |
Align Maestro iOS shards with e2e-build-ios so the artifact built against the iOS 26 SDK is executed on an iOS 26 simulator runtime instead of the macos-14 default (iOS 18.2). Cross-SDK execution surfaced an app crash on the rocketchat://room?path= deeplink that does not reproduce on the build-matched runtime.
|
Android Build Available Rocket.Chat Experimental 4.73.0.108901 Internal App Sharing: https://play.google.com/apps/test/RQVpXLytHNc/ahAO29uNQh6VvsjtzzmOd639neI-31XPTf_K-vXjBDTxIoegbfx4lvuJgJc76CYXq6F7yi0j9eyBM2Es0Q7Dx1zmBV |
|
iOS Build Available Rocket.Chat Experimental 4.73.0.108902 |
The macos-26 image does not ship an "iPhone 16 Pro" device; Boot Simulator failed with "Invalid device: iPhone 16 Pro" and aborted the shard before maestro could run. Enumerate the available simulator catalog and select the highest-numbered iPhone N Pro present so the workflow survives image rotation.
|
Android Build Available Rocket.Chat Experimental 4.73.0.108903 Internal App Sharing: https://play.google.com/apps/test/RQVpXLytHNc/ahAO29uNRCm1NlseKQgNjvjM3kdMrjI-p4inK5gzeYjtz6PVaTi2JKubKu_S--cqzoqVzffFfTUgpR3gCLx2TeZo6x |
|
iOS Build Available Rocket.Chat Experimental 4.73.0.108904 |
iOS 26 surfaces a system "Save Password?" prompt after successful native form submission. It is presented as a modal sheet on top of the rooms list, which blocks XCUITest hit-testing for elements underneath. Maestro's `extendedWaitUntil id: rooms-list-view-sidebar, enabled: true` therefore never resolves and shard 9 (e2e-encryption) times out on the macos-26 / iOS 26 runners (reliably green on macos-14 / iOS 18.2). Dismiss the prompt with "Not Now" if visible, gated on iOS, in both `helpers/login.yaml` (catches the modal immediately after submit) and `tests/e2ee/utils/navigate-to-e2ee-security.yaml` (catches it if the e2ee saga delays the modal past the login wait). On platforms or runs where the prompt does not appear, the `runFlow when` block is a no-op.
Conflict in .github/workflows/maestro-ios.yml: this branch's dynamic 'highest iPhone N Pro' picker supersedes develop's static SIM_NAME table (the macos-26 picker handled shard 14 fine in run 26065155352).
iOS Maestro tests were running each failing flow up to 8 times: nick-fields/retry max_attempts=2 multiplied by run-maestro.sh's internal 1 main + 3 rerun rounds. Deterministic failures wasted ~20 min/job; the wrapper also re-ran every flow in the shard, not just the failed one, since it had no flow-level state. Drop the wrapper on iOS (Android never had it) and lower MAX_RERUN_ROUNDS from 3 to 2 so the script-internal loop alone gives at most 3 runs per failing flow.
…ord flow The "Save Password?" system sheet that iOS 26 shows after a native password form submission is the root cause of the change-password.yaml shard-1 flake (8/8 failure screenshots in run 26117774450 show the same sheet overlaying rooms-list-view). Two gaps in b04b5ab covered only login.yaml: 1. The change-password submit (`change-password-view-set-new-password-button`) also surfaces the sheet. Nothing dismissed it there, so the subsequent `assertVisible: rooms-list-view` was blocked by the modal. 2. The sheet has variable latency on iOS 26 (~0.5s–7s observed). The pre-existing one-shot `runFlow when: visible:` in login.yaml fires immediately after rooms-list-view becomes visible and frequently misses a late-rendering sheet, so the next interactive assertion times out. Consolidate the dismiss into a shared `helpers/dismiss-save-password.yaml` that waits up to 8s for the sheet with `optional: true` (no-op on Android / iOS 18), then taps Not Now with `optional: true`. Call it from login.yaml, change-password.yaml (both after new-password submit and before the final re-login assertion), and the existing e2ee navigate helper.
iOS 26 surfaces a system "Save Password?" sheet asynchronously (0.5s–7s variable latency) after any secureTextEntry field submit. It overlays the app and blocks XCUITest hit-testing, causing Maestro change-password shard-1 to fail at rooms-list-view assertions 100% on macos-26 runners (8/8 failures in run 26117774450 show the same sheet over rooms-list-view). Research summary: iOS sim has no documented kill switch (confirmed by Detox #3761, Selenium #11426, Maestro #2089). Maestro has no auto-dismiss and no workspace-level onFlowStart hook (#2063). iOS only engages Password AutoFill on `secureTextEntry={true}` fields, regardless of `textContentType` value, except `oneTimeCode`. Setting `secureTextEntry={false}` declassifies the field and suppresses the offer entirely — the same trick `RegisterView` already uses at `register-view-password`. Apply this gating once at `FormTextInput.tsx` so every password field that uses the shared input (LoginView, ChangePasswordView, E2EE views, RoomInfoEditView, etc.) is covered. Visual mask + show/hide eye icon stay driven by the original `secureTextEntry` prop; only the native prop passed to the underlying TextInput is suppressed. Plaintext password rendering under E2E mirrors what RegisterView already does and is acceptable for test environments that use throwaway credentials. Revert b04b5ab + the dismiss-save-password helper added earlier on this branch — no longer needed since the prompt never surfaces in E2E builds. Note: TwoFactor, JoinCode, ProfileView ActionSheets and the generic ActionSheetContentWithInputAndSubmit use raw TextInput (not FormTextInput) and are not covered by this fix. They are not currently exercised by any failing iOS Maestro shard; same pattern can be applied locally there if a flow lands that exercises them on iOS 26.
iOS classifies a field as a credential for Password AutoFill / Save
Password via any of three signals: secureTextEntry, textContentType in
{password, newPassword, ...}, or autoComplete in {password, password-new,
...}. The previous patch flipped only secureTextEntry, so iOS still
classified LoginView and E2EEnterYourPasswordView (both hardcode
textContentType='password' + autoComplete='password' on iOS) as
credential fields and surfaced the "Save Password?" sheet on iPhone 17
Pro / iOS 26 fresh sims in CI, blocking shard 9 (e2e-encryption.yaml).
Extend the FormTextInput-level suppression to override all three under
RUNNING_E2E_TESTS on iOS so the underlying TextInput presents as a
neutral text field to UIKit. Visual masking and the show/hide eye icon
still follow the original secureTextEntry prop.
iOS shard 1's open-deeplink helper used `text: Open` which is a regex match. On iOS 26 cold-launch the alert title "Open in 'Rocket.Chat Experimental'?" also matches `Open`, and Maestro's index:0 hits the title bounds (~275, 485 pt) instead of the button (~277, 573 pt), so the dialog stays up and every flow in shard 1 chain-fails. Other shards launch the app via UI first so the dialog either does not appear or its hierarchy looks different — same regex happens to work there. Anchor the selector to `^Open$`. iOS Run Maestro Tests step was capped at 30 min after we dropped the nick-fields/retry wrapper (which gave 2x30=60 min). Shard 12 ran ~35 min and was killed mid-flight; raise to 45 to match the longest passing shard plus rerun budget. Android AVD snapshot generation step was capped at 30 min. A cache miss after the 4G→6G RAM bump pushed cold-boot + recreate past that. Raise to 45 so a one-time cache repopulate succeeds and future runs hit cache.
All 14 Android shards start in parallel and check actions/cache at the same instant. Nothing has been written yet, so every shard misses and runs its own "Generate AVD snapshot for cache" step — burning ~90s per shard on a 6 GB emulator boot the matrix could have shared. The repo is also at ~12.5 GB of caches (over the 10 GB cap), so the AVD cache is the LRU victim and gets evicted before the next run anyway. Add a single serial `e2e-seed-android-avd` job between `e2e-hold` and `e2e-run-android`. It restores or generates the AVD snapshot once, post-uploads, and then the matrix kicks off — so every shard's `Cache Android AVD` step now hits. The existing per-shard fallback stays as defense in depth for the (rare) eviction-between-jobs case.
…undle # Conflicts: # .github/scripts/run-maestro.sh
…em intact `echo -e` with double quotes interprets backslash escapes (`\n`, `\t`, `\\`, …) in the interpolated value. The Official keystore password contains a backslash, so the gradle.properties row was written with a mangled value — every signed APK build then failed with the misleading `KeystoreHelper: keystore password was incorrect`, when the real cause is `UnrecoverableKeyException: failed to decrypt safe contents entry`. The production composite at `.github/actions/build-android/action.yml` single-quotes the same two lines and signs successfully against the same repo-level secret. Match it. Also latent on develop after #7320 — worth back-porting.
Resolved conflicts: - .github/scripts/run-maestro.sh: accept develop's flattened APK name (app-release.apk) while keeping our monkey-warmup removal. - .github/workflows/e2e-build-android.yml: accept develop's full single-quoting of KEY_ALIAS/KEY_PASSWORD/BugsnagAPIKey, consistent with our keystore-password fix. Also refreshed the Experimental reference in .maestro/helpers/open-deeplink.yaml now that Experimental has been sunset on develop.
Proposed changes
CI infra follow-up #2 (commit 63db0eb)
e2e-seed-android-avdjob inbuild-pr.ymlbetweene2e-holdande2e-run-android. Every shard in the 14-job matrix was previously hittingactions/cachesimultaneously and missing — nothing had been written yet — so every shard ran its own "Generate AVD snapshot for cache" step. The seed job restores or generates the AVD snapshot once, post-uploads, and then the matrix fans out — every shard's cache restore now hits. The repo is also at ~12.5 GB of caches (over the 10 GB cap), so the AVD entry was the LRU victim across runs; the seed job re-populates on each PR run regardless. The per-shard fallback step stays as defense in depth.CI infra follow-up (commit 2066fbf)
Open in App?dialog tap selector in.maestro/helpers/open-deeplink.yamltotext: '^Open$'. UnanchoredOpenmatched both the iOS 26 alert title ("Open in 'Rocket.Chat Experimental'?") and the button; on cold-launch (shard 1) Maestro'sindex: 0picked the title at(275, 485) ptinstead of the button at~(277, 573) pt, the dialog persisted, and all 4 flows chain-failed. Other shards launch the app via UI first so the dialog either doesn't appear or its hierarchy looks different — same regex happened to work. Anchoring kills the ambiguity.Run Maestro Testssteptimeout-minutes30 → 45. Droppingnick-fields/retryremoved the previous 2×30=60 min ceiling; shard 12 ran 35 min on run 26168373950 and was killed mid-flight. 45 min matches the slowest passing shard (~27 min) plus rerun headroom.timeout-minutes30 → 45. A cache miss after the 4 GB → 6 GB RAM bump pushed cold-boot + AVD recreate past the cap on shard 3. Once the cache repopulates, future runs hit cache and skip this step.CI infra (Android & iOS)
Broken pipeinstall failures that were the dominant Android shard flake source.monkeywarmup inrun-maestro.sh(+ trailingsleep 6/am force-stop) — it stressedsystem_serverright before Maestro's driver install.-no-snapshot-saveto prevent corruption. Keyboard AVD (shard 14) untouched.android-emulator-runnersteps.jlumbroso/free-disk-space@v1.3.1(Android SDK preserved; ~6 GB freed).nick-fields/retrywrapper on iOS Maestro shards and lowerMAX_RERUN_ROUNDSinrun-maestro.shfrom 3 → 2. The compounded retry produced up to 8 reruns of each failing flow (outermax_attempts: 2× inner 1 main + 3 rerun rounds), wasting ~20 min/job on deterministic failures and re-running every flow in the shard on outer retry. New ceiling per failing flow: 3 runs (1 main + 2 rerun rounds). Android already called the script directly and inherits the new default.maestro-report.xml,maestro-rerun-round-*.xml) and the full~/.maestro/teststree — previously only screenshots were captured.macos-26+ Xcode 26.2.0 to matche2e-build-ios.yml. The artifact is built against the iOS 26 SDK; the priormacos-14runner booted iOS 18.2, which surfaced arocketchat://room?path=deeplink crash that does not reproduce on the build-matched runtime.App: sidebar testID fix (unblocks shard 8 + ~12 other flows)
SidebarViewwraps theScrollViewin a plainViewcarryingtestID='sidebar-view'. On Android,ScrollViewdoes not propagatetestIDto the native accessibility tree; a regularViewdoes. Folded in from #7319.App: profile rate-limit fix (shard 8, Android)
profile.yamlwas making three consecutiveusers.updateOwnBasicInfocalls; the endpoint is rate-limited per user. Collapsed into one combined submit (name, username, nickname, bio, email) before tapping submit. The password-confirm sheet andChangePasswordViewpath are preserved.App: form keyboard scroll fix (shard 6, iOS)
FormContainerswapsKeyboardAvoidingView + ScrollViewforKeyboardAwareScrollViewfromreact-native-keyboard-controller(already a dep). KAS scrolls the focusedTextInputabove the keyboard on focus change, preventing Maestro 2.5.1'stapOnfrom landing on the QWERTY row instead of the target field.App: iOS Save Password suppression at FormTextInput (shards 1 & 9, iOS)
iOS 26 surfaces a system
Save Password?sheet asynchronously (0.5s–7s variable latency) after anysecureTextEntry={true}field submit. It overlays the app and blocks XCUITest hit-testing, breaking shard 1 (change-password.yaml, 100% deterministic — 8/8 failures in run 26117774450 all show the sheet overrooms-list-view) and shard 9 (e2e-encryption.yaml). The earlier YAML-level dismiss (a one-shotrunFlow when: visible: 'Securely store your password') missed the late-arriving variant of the sheet and required per-test discipline.Researched alternatives (no clean sim/CLI/Maestro-config kill switch exists — confirmed by Detox #3761, Selenium #11426, Maestro #2089). iOS only engages Password AutoFill on
secureTextEntry={true}fields; setting it tofalsedeclassifies the field at the OS level and suppresses the offering entirely.RegisterViewalready uses this same pattern atregister-view-password.Fix: in
app/containers/TextInput/FormTextInput.tsx, whenRUNNING_E2E_TESTS === 'true'on iOS, suppress the nativesecureTextEntryprop passed to the underlyingTextInput. Visual masking and the show/hide eye icon stay driven by the original prop. One seam covers every password field that uses the shared input (LoginView, ChangePasswordView, E2EE views, RoomInfoEditView, etc.). Plaintext password rendering under E2E mirrors what RegisterView already does and is acceptable for test environments using throwaway credentials. The YAML dismiss block added in b04b5ab is reverted.Deferred (tracked separately)
room-last-message-thread-50-plus.yaml) — Maestro gesture injection exits RoomView mid-scroll; tracked as NATIVE-1128.jump-to-message.yaml) — pre-existing baseline failure; related regressions tracked as NATIVE-1125 (composer auto-focus) and [NATIVE-1126] (quoted message tap).Issue(s)
NATIVE-1123
Related (deferred, not fixed here): NATIVE-1125 · [NATIVE-1126] · NATIVE-1128
How to test or reproduce
Broken pipe,Monkey aborted, andFailed to install apkin shard logs.sidebar-viewstep and complete profile edits + password change in a single submit.helpers/create-account.yamlshould complete with a clean email field (no leadingt/tmobilecharacter corruption).change-password.yamlande2e-encryption.yamlshould complete on iOS 26 — noSave Password?sheet appears because everyFormTextInput-backed password field is declassified underRUNNING_E2E_TESTS=true.jump-to-message.yaml) is expected to remain red — pre-existing failure.Screenshots
n/a
Types of changes
Checklist
Further comments
The sidebar testID fix was originally opened as #7319 and folded here so CI stabilization and its dependency ship together (#7319 closed). Acceptance is measured over a 10-run window on
developafter this lands; tracked separately.Summary by CodeRabbit
Chores
Tests
Style