Skip to content

fix(ios): keep failed alert reads unresolved - #4187

Merged
thymikee merged 11 commits into
mainfrom
fix/alert-required-reads
Nov 4, 2026
Merged

thymikee merged 11 commits into
mainfrom
fix/alert-required-reads

Conversation

@thymikee

@thymikee thymikee commented Nov 3, 2026 •

Copy link
Copy Markdown
Member

Summary

Required Apple alert reads preserve uncertainty as ALERT_UNRESOLVED instead 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

  • Base 3d6ddf32adf6c9615df13a64488399a769d13761; behavior-tested head 187113225f2bc7ce60a60f73bad0b20fc095889a. The same native query-seam regression fails on base (empty get/wait success and ALERT_BUTTON_NOT_FOUND) and passes on head. Independent review found no concrete issue.
  • pnpm check:affected --run --head 42fe0b3971a0aaaa68573db26defe64f25b83cde passed, receipt run-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.
  • At 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.
  • Earlier controlled primary-host evidence remains attributed to 3d6ddf3; it does not prove natural AX failure or remote-host acquisition. Head live fixture evidence and limits are recorded separately.
  • A near-deadline action dispatched and timed out with unknown dispatch, although its handler ran once; it was not a success. Wait can miss prompts appearing in its final <=300 ms, and admitted work can outlive its budget. Hosted CI for 42fe0b3 remains pending.

@vercel

vercel Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
agent-device-website Ready Ready Preview Nov 4, 2026 10:24am UTC

Request Review

@github-actions

github-actions Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Size Report

Metric Base Current Diff
Installed (including dependencies) 5.81 MB 5.75 MB -61.1 kB
Package (unpacked) 5.81 MB 5.75 MB -61.1 kB
Package (download) 1.76 MB 1.75 MB -11.0 kB

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 28.3 ms 18.1 ms -10.2 ms
CLI --help 52.9 ms 48.3 ms -4.6 ms

@thymikee

thymikee commented Nov 3, 2026

Copy link
Copy Markdown
Member Author

Live verification completed at head d0f9fe7f765b595f1054108f68be1ab5a027cc83 on iPhone 18 Pro, iOS 27.0, UDID BDA9B772-A0A9-46A4-AC91-C9107FAE0277.

Branch provenance and isolation

  • Rebuilt with pnpm build.
  • Started the checkout daemon using this worktree’s node bin/agent-device.mjs devices --platform ios --daemon-server-mode http --state-dir "$PWD/.tmp/alert-live-followup-state", with AGENT_DEVICE_IOS_RUNNER_DERIVED_PATH="$PWD/.tmp/alert-live-derived".
  • Daemon metadata reports version: 0.21.37-dev, codeOrigin: checkout. Its runner log names this worktree’s .tmp/alert-live-derived/Build/Products/AgentDeviceRunner.env.session-…xctestrun.
  • Higher-priority session instructions require the Device panel’s bound CLI for UI actions. That client was pointed explicitly at the checkout daemon using --config "$PWD/.tmp/alert-followup-config.json" --state-dir "$PWD/.tmp/alert-live-followup-state" --session alert-required-live-final. Alert acquisition and activation therefore ran this branch’s daemon and native runner, rather than the installed release’s runner.

(a) Ordinary microphone permission prompt

Opened com.callstack.agentdevicelab and its agent-device-test-app:///automation route. Reset only this app’s microphone permission, then pressed automation-request-microphone.

alert get --json exited 0:

{"success":true,"data":{"items":["Don’t Allow","Allow"],"message":"Allow “Agent Device Tester” to access your microphone?","runnerMainThreadBusy":false}}

alert accept --json exited 0, returning "message": "accepted". A subsequent get text 'id="automation-microphone-permission"' returned granted. The runner log records exactly the Allow activation: action=accept label=Allow … point=(275.0,552.7).

(b) No prompt

Both before the prompt and after acceptance, alert get --json exited 1. Relevant error fields:

{"code":"COMMAND_FAILED","message":"alert not found","details":{"runnerErrorCode":"ALERT_NOT_FOUND","dispatched":"no"}}

The native runner’s nested error also has code: ALERT_NOT_FOUND.

(c) ALERT_UNRESOLVED

No live unreadable SpringBoard condition occurred: required reads completed normally. A read-only short-deadline attempt, alert get --timeout 1, was rejected locally with INVALID_ARGS because alert does not expose that flag; it did not reach the runner and is not counted as an unresolved result. Required-query exceptions and spent deadlines remain covered by the six passing native regression/control methods; restoring the old defaults makes all five regression cases fail while the empty-search control passes.

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 .tmp/alert-followup-* and .tmp/alert-live-followup-state/.

@thymikee
thymikee marked this pull request as ready for review November 3, 2026 11:43
@thymikee

thymikee commented Nov 3, 2026

Copy link
Copy Markdown
Member Author

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.

@cubic-dev-ai cubic-dev-ai Bot 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.

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-sentinel-staging

apex-sentinel-staging Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Apex Sentinel

Verdict: approve

Since the last review: 4 new · 5 still open · 8 no longer raised

9 finding(s): 6 minor, 3 nit. Verdict: approve.

🟡 Minor (6)

Where Finding
packages/platform-apple/src/alert.ts:26 🆕 alert.ts module doc still asserts ALERT_UNRESOLVED means a prompt is on screen, which the new .notActionable case breaks
apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SystemModal.swift:124 Blocking-modal search loses its per-iteration deadline checks and now reads one SpringBoard window at a time
apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SystemModal.swift:175 ALERT_UNRESOLVED widened so far that a non-empty button set can be reported as 'no actionable controls'
apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Alert.swift:158 A spent deadline on the alert get read reports ALERT_UNRESOLVED instead of ALERT_DEADLINE_EXCEEDED
apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Alert.swift:317 🆕 Disabled buttons are now offered as tap candidates and reported as hittable in the blocking-modal snapshot
apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SystemModal.swift:40 🆕 Disabled controls are reported as hittable in the blocking-system-modal snapshot
🔵 Nit (3)
Where Finding
apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Alert.swift:285 🆕 alertUnresolvedResponse() is missing a blank line before the following private helper
apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+BlockingSystemModalResolution.swift:18 resolveBlockingSystemModal silently swallows queryFailed reads
apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SystemModal.swift:145 readModalValue takes fallback nil as T?, so it cannot return an Optional that is legitimately nil unless T is already optional

Reply under an inline comment with @apex-sentinel-staging note ... or @apex-sentinel-staging pivot ... to re-check that finding.
Comment @apex-sentinel-staging review on this conversation to run a full review.

Earlier reviews

Review of 3d6ddf32 · 04.11.2026 09:21 UTC

Apex Sentinel

Verdict: approve

Since the last review: 8 new · 5 still open · 5 no longer raised

13 finding(s): 6 minor, 7 nit. Verdict: approve.

🟡 Minor (6)

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

@apex-sentinel-staging apex-sentinel-staging Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Apex Sentinel: 5 new findings on d0f9fe7. The verdict and the full list are in the summary comment.

@thymikee
thymikee force-pushed the fix/alert-required-reads branch from d0f9fe7 to a936488 Compare November 3, 2026 13:01
@thymikee

thymikee commented Nov 3, 2026

Copy link
Copy Markdown
Member Author

Review round completed at a9364884a99f369482116dc0694f5ccf9ad9cc07 after rebasing #4187 onto current main.

Rebase and scope

  • Old base dc39b316fa419a345ebac2beb1a99448bc247c74, old local head 98480f3820634824f0f68b05511546277477902c.
  • New base c8ad8fb1257787ce0973cf74271cb9a4fafb51d9. git range-diff reports the original three commits unchanged (=), followed by the remote-acquisition fix and a test-only fixture/stub deduplication.
  • 20 files, +429/-345 lines, net +84. This includes the unit-only disabled-action fixture and its small extraction from the already-large fixture host. Foldable tap routing is unchanged.

Review fixes and native regression proof

  • The shared resolved surface carries all acquired controls. Only alert action selection checks enabled status. Ordinary and raw system-modal snapshots retain disabled controls.
  • A SpringBoard mirror with no acquired controls requires a readable remote host. Missing required host acquisition returns unresolved instead of resolving the empty mirror. The dispatch regression covers get, accept, dismiss, and wait, and checks the fixture's activation counters remain zero.
  • The no-enabled-buttons test launches two genuinely disabled UIAlertActions, verifies both are disabled, then calls resolveAlert before inspection/action assertions. Restoring the old enabled-buttons-empty → absent branch in a disposable checkout made that test fail; restoring the fix passed (1 failed → 1 passed at 2f806e6fa441649c0de950a9071b604b655260d7, preserved unchanged by rebase).
  • At 8fd126b0d26ba4bede9d6c599f1355c4db76cf3f, 12 method-selected iOS XCTest tests passed. A disposable checkout then restored the enabled-only snapshot filter and missing-host fallback: 2 failed tests / 1 passed control, with 8 failing assertions. Both raw formats lost Cancel/OK; get/wait succeeded incorrectly and accept/dismiss returned the wrong code. The same three methods passed again on the fixed implementation. The disposable checkout was removed and implementation SHA/status/diff were unchanged.
  • The reported empty/nil-reader issue does not hold: the unchanged readModalValue passed empty [XCUIElement], optional nil, false, and Void. Its outer optional unwrap preserves a successful optional-nil payload. No reader change was made for that finding.
  • iOS unit/production and macOS production builds passed. Unit tests used AGENT_DEVICE_XCUITEST_INCLUDE_UNIT_TESTS=1, a separate .tmp/alert-unit-derived, and method-level -only-testing:AgentDeviceRunnerUITests/RunnerTests/<method>.

Live CLI evidence at 8fd126b0d26ba4bede9d6c599f1355c4db76cf3f

The final descendant commit changes only UnitTests/RunnerTests+AlertObservationTests.swift to remove repeated setup flagged by the native duplication gate. Production sources and the disabled fixture are byte-for-byte unchanged from the live-verified commit.

Device: iPhone 18 Pro, iOS 27.0, BDA9B772-A0A9-46A4-AC91-C9107FAE0277 only.

Built this checkout's CLI and native production runner. The fresh HTTP daemon used .tmp/alert-live-final-state; metadata reports codeOrigin: checkout, version 0.21.37-dev. The runner log names this worktree's .tmp/alert-live-derived/Build/Products/AgentDeviceRunner.env.session-…xctestrun.

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 --json

Permission 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 get

Initial 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, {"success":true,"data":{"message":"accepted","runnerMainThreadBusy":false}}; the app's permission value became granted. The runner log records one activation, action=accept label=Allow.

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, com.callstack.agentdevice.alert-required-fixture, leaving the production runner unchanged.

$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: {"success":true,"data":{"message":"First confirmation","items":["Cancel","OK"]}}.
Accept and dismiss each exited 1 with error.code: ALERT_BUTTON_NOT_FOUND, details.runnerErrorCode: ALERT_BUTTON_NOT_FOUND, and details.dispatched: no.
Both ordinary and raw CLI snapshots included:

[{"type":"Button","label":"Cancel","enabled":false},{"type":"Button","label":"OK","enabled":false}]

The app witness remained First actions: 0; replacement actions: 0 after both refusals. These CLI snapshots cover the app-owned fixture; the focused native test separately exercises the blocking-system-modal snapshot serializer in both formats.

Live unresolved limitation: no naturally unreadable SpringBoard/AccessorySetupUI host occurred on this simulator. alert wait 1 (valid positional 1 ms budget) exited 1 with runnerErrorCode: MAIN_THREAD_TIMEOUT, reason: runner_main_thread_timeout, dispatched: no: the outer execution watchdog won. This is not counted as ALERT_UNRESOLVED evidence. Required-query exceptions, spent deadlines, and missing required remote hosts are proven by the native dispatch/resolver tests; live get/action retry-window exhaustion on an unreadable remote host remains unverified.

Cleanup: own session closed, daemon stopped gracefully with --clean, temporary fixture uninstalled, assigned simulator confirmed Shutdown. No other device/session/worktree was changed. Full-repository format passed with no diff. The first rebased affected run stopped at four new duplicate blocks in the existing observation tests; those fixture/stub blocks were consolidated, without a baseline change, and the focused duplication gate then passed. At final head a9364884a99f369482116dc0694f5ccf9ad9cc07, all 18 method-selected native tests passed (0 failures), including the six affected observation methods. The assigned simulator was shut down again after that run. Cold self-review found no additional issue; no independent review was added in this round.

Final affected gate: pnpm check:affected --run --head a9364884a99f369482116dc0694f5ccf9ad9cc07 passed, receipt run-Ei98Uz. Remotely readable receipt summary:

{
  "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.

@apex-sentinel-staging apex-sentinel-staging Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

@thymikee

thymikee commented Nov 3, 2026

Copy link
Copy Markdown
Member Author

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.

@thymikee

thymikee commented Nov 3, 2026

Copy link
Copy Markdown
Member Author

Review cleanup at e07f062f32bad9d48c00bf5c8317a686de920bfd, atop a9364884a99f369482116dc0694f5ccf9ad9cc07.

Accepted findings:

  • Button selection now requires an explicit deadline; the only caller that omitted it was a unit test, which now passes a bounded deadline.
  • The decline-choice fixture helper has one consumer. It now lives inside that guarded app source, and the unused UnitTests header/import are removed. Existing packaging excludes the app fixture source; no new abstraction or exclusion was added.
  • The disabled-button fixture flag now names its replacement-alert scope, with both native test callers updated.
  • Documentation distinguishes failed/unfinished acquisition (ALERT_UNRESOLVED) from action/confirmation deadline exhaustion after resolution (ALERT_DEADLINE_EXCEEDED).
  • The owning system-modal catch again logs a tagged deadline diagnostic for typed ModalReadFailure.deadlineExceeded, without changing classification.

Remaining finding dispositions:

  • Remote-host fallback: the host list contains only AccessorySetupUI. An unreadable required mirror must remain unresolved; it cannot establish absence or authorize an app-alert fallback.
  • Stale button tolerance: skipping an unreadable required candidate could hide a higher-ranked choice and activate the wrong grant. Required reads remain fail-closed.
  • Unchanged activation / removed INTERACTION_FAILED: the predecessor already continued polling for .stillVisible; its terminal response arm was unreachable. No reachable reply or disclosure fixture was retired.
  • Informational title fallback: existing safe text-query/title fallback remains separate from required presence/action reads. The placeholder cannot satisfy launch-confirmation recognition.

Validation at this head:

  • Full repository format and both iOS builds pass. All 13 selected native tests pass, including chooser, decline-choice and both disabled-control paths; no native skips. The task's iPhone 18 Pro simulator was shut down.
  • AGENT_DEVICE_VITEST_MAX_WORKERS=2 pnpm check:affected --run --head e07f062f32bad9d48c00bf5c8317a686de920bfd passed. Receipt run-aGQKFb: exactCommit=true, checkoutChanged=false; 14 checks passed, nine skipped, zero failed. Selected Vitest 1,076 passed; unit Vitest 17,138 passed/one skipped; unit Node 76 passed/six skipped. The nine CI/manual lanes remain skips, not passes.
  • Independent cleanup review found no issues.
  • Cleanup is eight files, +34/-32; full PR is 19 files, +432/-346. Existing live evidence stays attributed to its original SHA; no new manual live session is claimed. Hosted checks and approval remain outstanding.

No inline review replies or thread resolutions were performed in this round.

@apex-sentinel-staging

Copy link
Copy Markdown

Apex Sentinel could not review commit e07f062f. No review ran, so there are no findings; this is not a verdict on the change.

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 @apex-sentinel-staging review to try again.

@thymikee

thymikee commented Nov 3, 2026

Copy link
Copy Markdown
Member Author

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.

@thymikee

thymikee commented Nov 3, 2026

Copy link
Copy Markdown
Member Author

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.

@thymikee
thymikee force-pushed the fix/alert-required-reads branch from e07f062 to 3d6ddf3 Compare November 4, 2026 09:09
@thymikee

thymikee commented Nov 4, 2026

Copy link
Copy Markdown
Member Author

Validation evidence for 3d6ddf32adf6c9615df13a64488399a769d13761 (rebased onto 9cf8168a4ac8770e3a0377fcd568bc3aff0a5a90): the exact-head affected receipt run-Co8SKu reports exactCommit=true, checkoutChanged=false, 1,106 related tests and 17,217 unit tests passed (one skipped), and 12 command-doc tests passed. Ten hosted or parked lanes were skipped, not local passes. A focused alert suite passed 21 tests. Independent review found no concrete issues.

A unit-enabled native compile and 12 tests passed at 1080463c60689143dc3fc9f0ab7a2a3b37ddf1bb, whose native component is source-identical to the final head; the later change fixed TypeScript wait behavior. A controlled instrumented native build was completed at the final head; instrumentation was not committed.

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 ALERT_UNRESOLVED with dispatched: no. The respective read markers were 1/14/6/6, with no activation and the prompt retained in each case; wait took 5,004 ms. This is a controlled primary-host substitute, not proof of a natural AX failure or remote-host acquisition. A separate eight-second pending leaf triggered the actual MAIN_THREAD_TIMEOUT watchdog and drained; natural absence timed out with ALERT_NOT_FOUND.

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.

@apex-sentinel-staging apex-sentinel-staging Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Apex Sentinel: 7 new findings on 3d6ddf3. The verdict and the full list are in the summary comment.

Comment thread packages/platform-apple/src/alert.ts Resolved
Comment thread packages/platform-apple/src/alert.ts Resolved
@thymikee

thymikee commented Nov 4, 2026

Copy link
Copy Markdown
Member Author

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 ALERT_UNRESOLVED with nothing dispatched and the prompt kept. It does not cover a natural accessibility failure or AccessorySetupUI acquisition, as the PR notes.

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.

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Nov 4, 2026
@thymikee

thymikee commented Nov 4, 2026

Copy link
Copy Markdown
Member Author

Publication evidence for 187113225f2bc7ce60a60f73bad0b20fc095889a:

A recognized app alert, Safari dialog or dismiss-popup window with no acquired action now produces typed notActionable, mapped to retriable ALERT_UNRESOLVED; it does not become an empty successful read or a fatal button-not-found result. Blocking system precedence and disabled-control acquisition remain unchanged.

The new native regression substitutes empty query results at the acquisition seam of a recognized fixture alert. On base 3d6ddf32adf6c9615df13a64488399a769d13761, get/wait succeeded empty and accept/dismiss returned ALERT_BUTTON_NOT_FOUND; on head the regression passed. A fresh unit-enabled native build and 14 selected native tests passed, as did 24 focused TypeScript tests. Independent review found no concrete issue. The exact-head affected gate passed with exactCommit=true and checkoutChanged=false (receipt run-EirkeU); hosted and parked lanes skipped locally remain obligations.

On iPhone 18 Pro, iOS 27.0, a private UIKit fixture used real delayed UIAlertAction attachment with no production fault instrumentation. A ready control accepted with one handler hit. In the 600 ms trial, two native reads completed before actual attachment; a subsequent attempt accepted successfully, the handler advanced exactly once, and the alert disappeared. Individual early error envelopes were not logged: their retriable classification follows from the production path, not captured messages. A permanently actionless alert exhausted retries with ALERT_UNRESOLVED, dispatched: no, and no handler hit. Earlier head get returned unresolved and a 12-second wait found the actual button; per-poll and attachment timestamps were not retained for that wait.

A 1,000 ms trial near the two-second action deadline dispatched, then returned MAIN_THREAD_TIMEOUT with dispatched: unknown; the handler ran once and native work drained. This was not a successful response and a new regression has not been established. No timeout budget was extended. The controlled primary SpringBoard read evidence belongs to 3d6ddf3 and does not establish natural AX failure or remote-host acquisition. Hosted CI must run at the new head.

@cubic-dev-ai cubic-dev-ai Bot 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.

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

Comment thread website/docs/docs/commands.md Resolved

@apex-sentinel-staging apex-sentinel-staging Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Apex Sentinel: 3 new findings on 42fe0b3. The verdict and the full list are in the summary comment.

Comment thread packages/platform-apple/src/alert.ts Resolved
@thymikee

thymikee commented Nov 4, 2026

Copy link
Copy Markdown
Member Author

At 42fe0b3, the code still looks good. Retrying an alert that has no actionable controls stays inside the existing wait budget and ends in ALERT_UNRESOLVED with nothing dispatched, with no endless loop or false resolution. The docs now name all three alert surfaces and keep the inspection and retry guidance, so the CLI help and commands docs threads are fixed. Your earlier live run, with delayed controls and a permanently actionless alert, still covers this behavior.

Not blocking, take or leave: both consumers treat the new notActionable outcome exactly like unresolved. The acquisition guard could return the existing unresolved outcome, which would drop the extra case and its switch arms.

No conflicts are known.

@thymikee
thymikee merged commit 9ec5460 into main Nov 4, 2026
21 checks passed
@thymikee
thymikee deleted the fix/alert-required-reads branch November 4, 2026 12:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant