Repository navigation
fix(ios): keep failed alert reads unresolved - #4187
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Size Report
Startup median (7 runs, lower is better):
|
|
Live verification completed at head Branch provenance and isolation
(a) Ordinary microphone permission prompt Opened
{"success":true,"data":{"items":["Don’t Allow","Allow"],"message":"Allow “Agent Device Tester” to access your microphone?","runnerMainThreadBusy":false}}
(b) No prompt Both before the prompt and after acceptance, {"code":"COMMAND_FAILED","message":"alert not found","details":{"runnerErrorCode":"ALERT_NOT_FOUND","dispatched":"no"}}The native runner’s nested error also has (c) ALERT_UNRESOLVED No live unreadable SpringBoard condition occurred: required reads completed normally. A read-only short-deadline attempt, The earlier open failure’s log showed a successful build followed by test startup without a listener before the client’s 90-second timeout; no deeper terminal launch error was logged. This fresh isolated session opened in 4.030 s without source changes. Cleanup: session closed; daemon stopped gracefully; retained runners cleaned; device claims empty; only the specified simulator was shut down. Raw JSON and logs remain under this worktree’s |
|
At d0f9fe7 there are two behavior problems, and the live device run is missing. A visible SpringBoard mirror with no actionable elements can produce a resolved alert with no buttons when the required remote-host probe cannot establish a readable host. This turns the unreadable prompt case protected by #4149 into a successful get or wait, an immediate ALERT_BUTTON_NOT_FOUND on accept or dismiss, and potentially a snapshot of the empty mirror. The shared AlertResolution owner must distinguish failed required host acquisition from a readable alert with disabled buttons and keep the former unresolved; please add a resolver or dispatch regression for the host failure. Code Could the shared resolved surface retain all acquired controls and apply enabled-button eligibility only at the alert command boundary? The new filter removes a hittable disabled system-modal control before snapshot serialization, so ordinary and raw snapshots can lose its enabled:false state, and a successful modal-only probe prevents later capture recovery. Please let snapshotForBlockingSystemModal consume the acquired controls, including disabled ones. Code Live CLI verification at head is still needed for the daemon-to-runner route. Please build the head runner and daemon and record the SHA, device, exact commands and JSON outputs for permission get and accept followed by empty get; also reach unresolved acquisition and a readable alert with disabled buttons. Show unresolved and dispatched:no through get and exhausted wait and action windows, empty-button get and ALERT_BUTTON_NOT_FOUND without activation for the readable alert, and snapshot preservation of disabled controls after the fix. Code Could one acquired-surface result carry raw controls and explicit acquisition success or failure, resolve the remote host there, and derive enabled buttons at the command boundary while snapshots use the raw controls? Adjust the shared surface before replacing its snapshot consumers, and preserve the prior unresolved remote-host invariant. Not blocking: The test improvements can be taken or left: use actually disabled fixture actions through executeOnMainPrepared or resolveAlert, and separately let SpringBoard complete before injecting an app-owned required-query failure. Coverage is in progress; the Linux job covers the unit and provider suite, dispatch fixture consumers and coverage gates, but does not prove Swift device behavior. No conflicts are known. Before merge, fix the remote-host classification and snapshot control loss, obtain the live evidence, and require Coverage to pass at the fixed head. |
There was a problem hiding this comment.
All reported issues were addressed across 14 files
Reply with feedback, questions, or to request a fix.
View guided diff | Turn on auto-fix | Re-trigger cubic
Apex SentinelVerdict: Since the last review: 4 new · 5 still open · 8 no longer raised 9 finding(s): 6 minor, 3 nit. Verdict: approve. 🟡 Minor (6)
🔵 Nit (3)
Reply under an inline comment with Earlier reviewsReview of
|
| Where | Finding |
|---|---|
apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Alert.swift:151 |
Alert inspection drops every button when one label read fails |
apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Alert.swift:54 |
🆕 Removing the empty-action guard makes a recognized surface with no actionable button resolve as an empty alert instead of absence |
apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Alert.swift:137 |
Dropped .stillVisible makes a no-op tap wait out the whole deadline |
apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+BlockingSystemModalResolution.swift:5 |
🆕 resolveBlockingSystemModal now short-circuits the app's own alert reads when the SpringBoard surface has no enabled buttons |
apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+BlockingSystemModalResolution.swift:20 |
The new required-read queryFailed path logs nothing, unlike the deadline path it sits beside |
apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SystemModal.swift:175 |
🆕 readModalActions no longer counts the merged descendant list, so a remote-hosted modal is never probed |
🔵 Nit (7)
| Where | Finding |
|---|---|
apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SystemModal.swift:113 |
Removed ALERT_DEADLINE_EXCEEDED guard lets a SpringBoard search start after the probe deadline and throw out of the snapshot capture |
apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SystemModal.swift:145 |
🆕 readModalValue cannot tell a failed read from a legitimate nil |
packages/platform-apple/src/alert.ts:101 |
🆕 awaitAppleAlert can return a successful read that lands after the caller's timeout |
apple/runner/AgentDeviceRunner/AgentDeviceRunner/AgentDeviceRunnerAppFixtures.m:396 |
Disabled-button fixture flag also disables the replacement alert's buttons, so the new test only covers the first alert |
apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Alert.swift:71 |
🆕 alertUnresolvedResponse hides whether the search failed or ran out of time |
apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Alert.swift:152 |
🆕 alert get title now degrades to "Alert" silently where the predecessor let the read failure surface |
packages/platform-apple/src/alert.ts:117 |
🆕 Same budget quantity computed two ways in awaitAppleAlert |
Review of a9364884 · 03.11.2026 13:16 UTC
Apex Sentinel
Verdict: request_changes
Since the last review: 8 new · 2 still open · 3 no longer raised
10 finding(s): 1 major, 5 minor, 4 nit. Verdict: request_changes.
Required fixes
- remoteHostedSystemModal lost the per-host fallback: one flaky host read now aborts the whole probe
🟠 Major (1)
| Where | Finding |
|---|---|
apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+BlockingSystemModalResolution.swift:30 |
remoteHostedSystemModal lost the per-host fallback: one flaky host read now aborts the whole probe |
🟡 Minor (5)
| Where | Finding |
|---|---|
apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Alert.swift:303 |
🆕 Action reads moved from per-element tolerance to all-or-nothing, so a single stale button turns a readable alert into ALERT_UNRESOLVED |
apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerAlertVerification.swift:16 |
🆕 An activation that leaves the alert unchanged now burns the whole deadline and reports ALERT_DEADLINE_EXCEEDED |
apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Alert.swift:71 |
🆕 An expired deadline now reports ALERT_UNRESOLVED on the get/wait path while the accept path still reports ALERT_DEADLINE_EXCEEDED |
apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+AlertObservation.swift:77 |
🆕 Removed INTERACTION_FAILED "unchanged alert presentation" refusal leaves no disclosure row |
apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Alert.swift:303 |
🆕 chooseAlertButton's deadline: .distantFuture default disables the timeout it exists to enforce |
🔵 Nit (4)
| Where | Finding |
|---|---|
apple/runner/AgentDeviceRunner/AgentDeviceRunner/AgentDeviceRunnerAppFixtures.m:14 |
🆕 App-target fixture source now imports a header out of the UI-test target's UnitTests/ directory |
apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Alert.swift:157 |
🆕 Unreadable alert title becomes a fabricated "Alert" message while an unreadable button list returns ALERT_UNRESOLVED |
apple/runner/AgentDeviceRunner/AgentDeviceRunner/AgentDeviceRunnerAppFixtures.m:378 |
🆕 Disabled-button fixture flag is only honored on the replacement-alert path |
apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SystemModal.swift:150 |
Removed AGENT_DEVICE_RUNNER_SYSTEM_MODAL_PROBE_DEADLINE stage=sheets diagnostic with no replacement log at the new deadline path |
Review of d0f9fe7f · 03.11.2026 11:57 UTC
Apex Sentinel
Verdict: request_changes
5 finding(s): 1 major, 2 minor, 2 nit. Verdict: request_changes.
Required fixes
- System-modal snapshot now drops disabled/grayed-out controls (enabled filter leaked into the snapshot path)
🟠 Major (1)
| Where | Finding |
|---|---|
apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SystemModal.swift:18 |
System-modal snapshot now drops disabled/grayed-out controls (enabled filter leaked into the snapshot path) |
🟡 Minor (2)
| Where | Finding |
|---|---|
apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SystemModal.swift:113 |
Deadline telemetry for the SpringBoard probe removed; second (sheet) enumeration now always starts and the whole probe degrades to ALERT_UNRESOLVED |
apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SystemModal.swift:145 |
readModalValue turns an empty actions query into a hard failure, dropping a mirrored SpringBoard modal to ALERT_UNRESOLVED |
🔵 Nit (2)
| Where | Finding |
|---|---|
apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SystemModal.swift:150 |
readModalValue forfeits an optional-returning read as a failure; correctness rests on callers all returning non-optional values |
apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerAlertVerification.swift:19 |
verify returns nil for "unchanged presentation" with no name or comment for the sentinel |
There was a problem hiding this comment.
Apex Sentinel: 5 new findings on d0f9fe7. The verdict and the full list are in the summary comment.
d0f9fe7 to
a936488
Compare
|
Review round completed at Rebase and scope
Review fixes and native regression proof
Live CLI evidence at The final descendant commit changes only Device: iPhone 18 Pro, iOS 27.0, Built this checkout's CLI and native production runner. The fresh HTTP daemon used Higher-priority device instructions require the Device panel's bound client. All UI commands below used its exact returned path, explicitly configured to this checkout's daemon/native runner: BOUND=/Users/michal/.t3/userdata/device/targets/t3-df31407891b72744251843c6/agent-device
# Every command also has:
# --config "$PWD/.tmp/alert-final-config.json"
# --state-dir "$PWD/.tmp/alert-live-final-state"
# --session alert-required-rebased-live --jsonPermission flow: $BOUND open com.callstack.agentdevicelab --foreground --timeout 120000
$BOUND alert get
$BOUND settings permission reset microphone --app com.callstack.agentdevicelab
$BOUND open com.callstack.agentdevicelab agent-device-test-app:///automation --foreground
$BOUND scroll down --until 'id="automation-request-microphone"' --settle
$BOUND press '@e17~s401316'
$BOUND alert get
$BOUND alert accept
$BOUND get text 'id="automation-microphone-permission"'
$BOUND alert getInitial open succeeded in 3.366 s. Prompt get exited 0: {"success":true,"data":{"message":"Allow “Agent Device Tester” to access your microphone?","items":["Don’t Allow","Allow"],"runnerMainThreadBusy":false}}Accept exited 0, Both normal no-prompt reads (before and after permission acceptance) exited 1: {"success":false,"error":{"code":"COMMAND_FAILED","message":"alert not found","details":{"runnerErrorCode":"ALERT_NOT_FOUND","dispatched":"no"}}}Disabled fixture flow: installed a separately identified copy of the unit fixture host, $BOUND open com.callstack.agentdevice.alert-required-fixture --relaunch --launch-args --agent-device-alert-replacement-regression --launch-args --agent-device-alert-disabled-buttons --foreground
$BOUND alert get
$BOUND alert accept
$BOUND snapshot
$BOUND snapshot --raw
$BOUND alert dismiss
$BOUND get text 'id="agent-device-alert-actions"'Get exited 0: [{"type":"Button","label":"Cancel","enabled":false},{"type":"Button","label":"OK","enabled":false}]The app witness remained Live unresolved limitation: no naturally unreadable SpringBoard/AccessorySetupUI host occurred on this simulator. Cleanup: own session closed, daemon stopped gracefully with Final affected gate: {
"status": "passed",
"head": "a9364884a99f369482116dc0694f5ccf9ad9cc07",
"exactCommit": true,
"checkoutChanged": false,
"passed": [
"format",
"lint",
"typecheck",
"layering",
"fallow",
"native-dupes",
"build",
"vitest-related",
"unit",
"command-docs",
"docs-links",
"docs-prose",
"xctest-selection",
"packaged-runner-swift"
],
"skipped": [
"swift-runner-ios",
"swift-runner-macos",
"web-smoke",
"replay-ios",
"replay-ios-device",
"replay-macos",
"replay-linux",
"linux-command-evidence",
"replay-android"
]
}Related Vitest: 139 files / 1,076 tests passed. Skipped native/device/web replay lanes are CI obligations, not local passes. This round is shipped in one rebased push. CI at the new head, including Coverage and native/device lanes, remains an obligation; the coordinator handles checks/review watches. |
There was a problem hiding this comment.
Apex Sentinel: 8 new findings and 2 moved to a new line on a936488. The verdict and the full list are in the summary comment.
|
At a936488, the two behavior defects from the earlier review are fixed, but the requested live evidence for unreadable host acquisition and retry exhaustion is still missing. Could you run a head-equivalent checkout daemon and runner on a device that reaches unreadable required remote-host acquisition, and share the source SHA, build provenance, device, exact CLI commands, and exit and JSON results? Get must return ALERT_UNRESOLVED with dispatched:no; wait and accept/dismiss must exhaust their retry windows with the same code and no activation. Use a budget that reaches native acquisition: the posted 1 ms attempt reached the outer MAIN_THREAD_TIMEOUT watchdog. The posted permission, empty-state, disabled-alert, and snapshot results cover those neighboring paths; native fault tests do not establish live transport and host retries. Required host acquisition Four lower-priority threads still apply. The disabled actions thread is fixed by the resolver regression, and the other threads from the last round no longer apply at this head. All other checks pass; the only one not passing is the external review bot status, which runs no repository code. There are no conflicts. Before merge, please post the live unresolved-host evidence above. |
|
Review cleanup at Accepted findings:
Remaining finding dispositions:
Validation at this head:
No inline review replies or thread resolutions were performed in this round. |
|
Apex Sentinel could not review commit Apex Sentinel hit an internal error and could not finish this review. This is a problem on Sentinel's side, not with the change; if it keeps happening, tell whoever runs Sentinel for this repository. Push a new commit or comment |
|
The two earlier behavior defects are fixed at e07f062, but the live unreadable-host evidence requested in the earlier review is still missing. Native fault tests cover the runner but do not establish daemon transport or host retry exhaustion. Please post a head-equivalent device run that reaches unreadable required host acquisition, with the source SHA, build provenance, device, exact CLI commands and exit/JSON results. Get must return ALERT_UNRESOLVED with dispatched:no; wait and accept/dismiss must exhaust their retry windows with the same code and no activation. Please use a budget that reaches native acquisition: the 1 ms attempt reached MAIN_THREAD_TIMEOUT instead. Existing permission, empty, disabled and snapshot evidence need not be repeated. Required acquisition The disabled actions thread is fixed at this head, and the other threads from the last round no longer apply. The only failing check is the external review bot status, which errored before it ran and runs no repository code. There are no conflicts. Before merge, please post the live run above. |
|
This branch now conflicts with main. Please rebase and resolve the conflicts; the review at e07f062 still applies, and the live unreadable-host run is still the remaining evidence. |
e07f062 to
3d6ddf3
Compare
|
Validation evidence for A unit-enabled native compile and 12 tests passed at On a real visible permission prompt, a test-instrumented Objective-C exception at the required primary SpringBoard read made get, wait (5,000 ms), accept and dismiss return Wait retains the last completed observation and stops admitting polls in the final <=300 ms. A prompt appearing only in that interval can be missed; this admission policy does not guarantee an admitted read finishes before the deadline. |
There was a problem hiding this comment.
Apex Sentinel: 7 new findings on 3d6ddf3. The verdict and the full list are in the summary comment.
|
At 3d6ddf3, the code looks good, and the earlier conflict is fixed. A failed modal read stays unresolved through get, wait, accept and dismiss, and alert-wait read admission is now checked against the remaining budget before it starts. The controlled SpringBoard exception run shows One limit worth knowing: the shared wait loop does not cancel admitted work and can miss a prompt that appears only in its final 300 ms. That is documented and does not block this change. The review threads, including the deadline budget and disabled actions threads, no longer apply at this head. All 21 checks pass, and no conflicts are known. |
|
Publication evidence for A recognized app alert, Safari dialog or dismiss-popup window with no acquired action now produces typed The new native regression substitutes empty query results at the acquisition seam of a recognized fixture alert. On base On iPhone 18 Pro, iOS 27.0, a private UIKit fixture used real delayed A 1,000 ms trial near the two-second action deadline dispatched, then returned |
There was a problem hiding this comment.
All reported issues were addressed across 8 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
View guided diff | Turn on auto-fix | Re-trigger cubic
There was a problem hiding this comment.
Apex Sentinel: 3 new findings on 42fe0b3. The verdict and the full list are in the summary comment.
|
At 42fe0b3, the code still looks good. Retrying an alert that has no actionable controls stays inside the existing wait budget and ends in Not blocking, take or leave: both consumers treat the new No conflicts are known. |
Summary
Required Apple alert reads preserve uncertainty as
ALERT_UNRESOLVEDinstead of reporting absence or choosing an action from incomplete information. Recognized app alerts, Safari dialogs and dismiss-popup windows with no acquired actions also stay unresolved and retriable. Disabled controls remain visible; action selection uses enabled controls. Blocking system prompts retain precedence. Follow-up to #4149. The change touches 22 files across the runner, contracts, tests and command documentation.Validation
3d6ddf32adf6c9615df13a64488399a769d13761; behavior-tested head187113225f2bc7ce60a60f73bad0b20fc095889a. The same native query-seam regression fails on base (empty get/wait success andALERT_BUTTON_NOT_FOUND) and passes on head. Independent review found no concrete issue.pnpm check:affected --run --head 42fe0b3971a0aaaa68573db26defe64f25b83cdepassed, receiptrun-LpkO2Y(exactCommit=true,checkoutChanged=false): 14 passed, one deduplicated, 10 hosted or manual lanes skipped. Related tests: 1,106 passed; unit tests: 17,220 passed, one skipped. Hosted and parked lanes remain obligations.187113225f2bc7ce60a60f73bad0b20fc095889a, the native unit-enabled build and 14 selected native tests passed; focused TypeScript: 24 passed. The new head changes only CLI help and command documentation, naming all three recognized surfaces; no native behavior or tests changed.3d6ddf3; it does not prove natural AX failure or remote-host acquisition. Head live fixture evidence and limits are recorded separately.42fe0b3remains pending.