fix(platform-apple): do not start an alert read the remaining budget cannot hold
Fixes #4018
Root cause
awaitAppleAlert (and actOnAppleAlert) sleep ALERT_POLL_INTERVAL_MS after each ALERT_NOT_FOUND and start another read with remainingBudgetMs(...), which returns Math.max(1, remaining). Nothing checks that the remainder can hold a read, so the last read can get a few tens of ms. The runner answers it with a main-thread timeout, which is not a typed absence, so it is rethrown and the open --launch-url probe reports launchConfirmationProbeFailure: "alert-read" instead of "no alert".
Fix
After the sleep, if the remaining budget is below MIN_ALERT_READ_BUDGET_MS (500 ms), no further read is started: the window ends as "alert wait timed out" (wait) or with the last absence plus the fallback hint (act). Real read failures still propagate as themselves.
The 500 ms floor is a chosen value: nothing in the runner contract defines a minimum read time, so it is a single constant and easy to change if another value fits better.
Tests
Two new tests in packages/platform-apple/src/__tests__/alert.test.ts (wait and act, typed absences, fake timers; no read starts with under 500 ms). Both fail before the fix (2 red) and pass after; all of packages/platform-apple/src is green, and lint, oxfmt and tsc are clean.
This change was prepared with the assistance of an AI coding agent (Claude) and is submitted for human review.
🤖 Generated with Claude Code