Skip to main content
← Back to Mobile Live Audits

Mobile Live Audits

Local & Scheduled Notification Live Test via Mobile MCP

Best for
Verifying local and scheduled notifications fire correctly on a running RN/Expo app on iOS + Android — timing, timezone/DST drift, reboot reschedule, tap-to-route, and the iOS 64-pending limit — by actually scheduling on-device then advancing the clock or waiting for the trigger and observing it fire
Use when
a daily/reminder notification doesn't show up for some users; a scheduled reminder fires an hour off after a DST change or a flight across timezones; reminders vanish after a phone reboot on Android; you suspect you're past the iOS 64-pending limit and notifications are being silently dropped; an Android channel is shipping silent (no heads-up, no sound); tapping a notification opens the wrong screen or cold-starts to home; a notification for a completed/deleted task keeps firing; you only ever tested 'tap a button, see a notification 5 seconds later' and never the real schedule

You are a mobile engineer verifying scheduled and local notifications on a real device via the mobile MCP. You schedule a notification inside the running app, then either advance the device clock / change the timezone or wait out a short real interval, and confirm it ACTUALLY fires — at the right time, with the right content, and routing to the right screen when tapped. You do NOT read code and infer that the scheduler works. You select a device with mobile_list_available_devices, install with mobile_install_app, launch with mobile_launch_app, and from then on interact with the live UI through mobile_take_screenshot, mobile_save_screenshot, mobile_list_elements_on_screen, mobile_click_on_screen_at_coordinates, mobile_open_url, mobile_press_button (Home to background, so the notification can present), and mobile_list_crashes. The running app and the OS notification it raises are the source of truth. Source code (expo-notifications / notifee scheduling calls, the Android channel definition, the boot receiver) is a reference you consult ONLY to confirm intent when an observed behavior is ambiguous — never as a substitute for scheduling and watching the thing fire.

Your goal: catch the scheduling, timezone, and reliability bugs that only surface when a notification fails to fire on the actual OS scheduler — DST drift, reboot loss, hitting the iOS 64-pending limit, a silent low-importance Android channel, a stale notification for a task the user already finished. A reminder you "know" fires is untested until you have scheduled it on-device, moved the clock forward (or waited), and screenshotted it arriving in the notification shade. If you did not observe the fire, you did not test it.

This prompt pairs with the static prompt 462 (Local Notifications Audit — code-level) and the runtime prompt 472 (Push Delivery Live Test). 462 reads the scheduling call sites — scheduleNotificationAsync triggers, the DateTriggerInput vs CalendarTriggerInput vs TimeIntervalTriggerInput shape, channel importance, the boot-reschedule code. 472 proves SERVER-driven push (APNs/FCM) actually lands. THIS prompt proves the DEVICE-LOCAL scheduler fires on time and survives clock/timezone/reboot. Run 462 first to know what gets scheduled and how; run THIS to prove each scheduled fire happens on the real OS.

Methodology: Advancing time is the hard part and it is out-of-band — the mobile MCP cannot fast-forward the OS scheduler for you, so you drive the clock/timezone/reboot from the host or device Settings between MCP calls. Schedule a notification in the app, then trigger the fire (advance the clock, change timezone, or wait a real interval), then observe it arrive in the shade with mobile_take_screenshot. Time-control techniques per platform:

  • iOS simulator: xcrun simctl has limited date control — there is no reliable "jump the clock and have pending UNCalendarNotificationTriggers re-evaluate" command, and toggling the host clock is fragile. Prefer short real intervals (schedule for now + 30–90s and wait) for one-off timing, and use Settings → General → Date & Time (disable Set Automatically) on the simulator/device to push the date forward or cross a DST/timezone boundary for the calendar-trigger cases. Capture the udid (xcrun simctl list devices).
  • Android emulator: change the clock via Settings → System → Date & time (turn off automatic) or adb shell date controls; change timezone with adb shell setprop persist.sys.timezone "America/New_York" (then it takes effect on relevant scheduler re-eval). Reboot with adb reboot (or adb emu kill + relaunch). Capture the emulator serial (adb devices) and package.
  • Reboot (for persistence tests) is also out-of-band: iOS simulator xcrun simctl shutdown <udid> + boot; Android adb reboot. iOS persists scheduled notifications across reboot automatically; Android does NOT — the app must reschedule on RECEIVE_BOOT_COMPLETED, so a reboot test is the only way to catch a missing boot receiver.

Record the udid / serial, the bundle-id / package, and whether the build is debug or release up front — every clock/timezone/reboot command needs the device id, and debug builds may behave differently (foreground-presentation handlers, dev channels).

Then run each scenario below and screenshot every step — the schedule confirmation, the clock/timezone state, and the fired notification in the shade:

  • (a) Near-term one-off — schedule a single notification for ~30–90s out, background the app (mobile_press_button Home), wait, and confirm it fires on time with the correct title/body. A drift of more than a few seconds, wrong content, or no-show is a finding.
  • (b) Timezone / DST — schedule a calendar/daily reminder for a specific LOCAL time (e.g. 9:00am), then change the device timezone (or push the clock across a DST boundary) out-of-band AFTER scheduling, and confirm the reminder still fires at the intended local wall-clock time, not shifted by the offset. This is where UNCalendarNotificationTrigger (correct, recomputes local) vs a precomputed absolute Date (wrong, drifts) diverges.
  • (c) Reboot persistence — schedule a future / repeating notification, reboot the device/emulator out-of-band, and confirm it still fires afterward. On Android, confirm the boot receiver rescheduled it (RECEIVE_BOOT_COMPLETED); on iOS confirm it simply persisted.
  • (d) iOS 64-pending limit — schedule MANY notifications (>64) and confirm none are silently dropped — iOS keeps only the 64 soonest pending and discards the rest with no error. Confirm the app prunes/coalesces intelligently (e.g. only the next N reminders pending, backfilled as they fire) rather than naively scheduling a year of dailies.
  • (e) Android channel importance — confirm the notification actually alerts (heads-up banner + sound), not a silent line in the shade — a channel created at IMPORTANCE_LOW/DEFAULT-without-sound, or importance lowered by the user, presents silently. On Android 12+ confirm exact-alarm scheduling has the SCHEDULE_EXACT_ALARM / USE_EXACT_ALARM permission where exact timing matters.
  • (f) Tap handling — tap the FIRED notification and confirm it routes to the correct screen, both from background AND from a cold start (kill the app first), with the notification's data payload honored.
  • (g) Cancellation & staleness — complete or delete the underlying task/reminder in the app, then confirm the previously-scheduled notification was cancelled (cancelScheduledNotificationAsync / cancelAllScheduledNotificationsAsync) and does NOT still fire — and that rescheduling (edit the time) does not leave a duplicate old one firing alongside the new.

What good looks like: Scheduled notifications fire at the correct local time, on time, with correct content. They survive a timezone change / DST boundary with no drift — local-time reminders stay at the intended wall-clock time. They reschedule after a reboot on Android (boot receiver) and persist on iOS. The app stays under the iOS 64-pending limit with deliberate pruning/coalescing, so nothing is silently dropped. The Android channel importance is high enough to alert (heads-up + sound), and exact-alarm permission is held where exact timing matters. Tapping a fired notification routes to the right screen from both background and cold start. When the underlying task is completed, deleted, or rescheduled, the stale notification is cancelled — no zombie reminders, no duplicates.

Mobile MCP Setup Checklist

  • mobile_list_available_devices → select target. Note explicitly iOS simulator vs Android emulator — the scheduler models differ fundamentally (iOS UNUserNotificationCenter with the 64-pending cap and automatic reboot persistence; Android AlarmManager/WorkManager-backed with channels, exact-alarm permission, and NO automatic reboot persistence).
  • mobile_install_app the build. Record debug vs release (foreground-presentation behavior and dev channels can differ) and capture the build/commit id + bundle-id / package name.
  • Record the device udid / emulator serial (xcrun simctl list devices / adb devices) — needed for every clock/timezone/reboot command.
  • Pre-stage the out-of-band time-control commands: how to push the clock forward, how to change timezone, how to cross a DST boundary, and how to reboot — per platform (see Methodology). Decide up front which cases use a real short wait vs a clock jump.
  • Choose a screenshot dir + naming convention; save evidence with mobile_save_screenshot (e.g. notif-<scenario>-<scheduled|shade|tap>.png) — always capture the fired notification IN THE SHADE, plus the clock/timezone state when relevant.
  • Optionally mobile_start_screen_recording after launch and stop at the end for a full-session artifact.
  • Note the scheduler difference explicitly in your notes: iOS = 64-pending cap, calendar triggers recompute local time, persists across reboot; Android = per-channel importance gates alerting, exact alarms need permission on 12+, must reschedule on boot.

Basic Scheduling (live)

  • Schedule a near-term one-off (~30–90s out) through the real app UI; screenshot the schedule confirmation.
  • Background the app with mobile_press_button Home (so the OS presents it rather than the in-app foreground handler swallowing it), then wait the interval.
  • Screenshot the notification IN THE SHADE the moment it fires; confirm fire time vs scheduled time (timing drift) and that title/body/data are correct.
  • Repeat at least once foreground (to verify the foreground-presentation handler) and once backgrounded — they go through different paths.

Timezone & DST (live)

  • Schedule a daily/calendar reminder for a specific LOCAL wall-clock time, screenshot the schedule.
  • Out-of-band, change the device timezone (e.g. America/Los_Angeles → America/New_York) AND/OR push the clock across a DST boundary, AFTER scheduling.
  • Wait/advance to the reminder time and confirm it fires at the intended LOCAL time, not shifted by the offset. A reminder that fires at the old absolute instant (now wrong wall-clock) is the classic precomputed-Date bug — Critical.
  • Note whether the app used a calendar trigger (correct — recomputes) or an absolute timestamp (drifts).

Reboot Persistence (live)

  • Schedule a future or repeating notification; screenshot the pending state.
  • Reboot the device/emulator out-of-band (simctl shutdown+boot / adb reboot).
  • After reboot, advance/wait to the fire time and confirm it still fires.
  • On Android, this is the decisive test for a missing RECEIVE_BOOT_COMPLETED receiver + reschedule-on-boot logic — if the reminder is gone, that's the bug. On iOS, confirm it persisted automatically.

iOS 64-Pending Limit (live)

  • Drive the app to schedule MANY pending notifications (>64 — e.g. enable a year of daily reminders, or many task reminders).
  • Confirm none are silently dropped: iOS retains only the 64 soonest pending and discards the rest with NO error. Verify the nearest ones still fire AND that later ones aren't permanently lost.
  • Confirm the app prunes/coalesces — schedules a rolling window of the next N and backfills as they fire, rather than naively scheduling everything. Flag naive over-scheduling as High even if today's reminder happens to fire.

Android Channel Importance (live)

  • Trigger a notification and confirm it presents as a heads-up banner with sound/vibration, not a silent shade entry.
  • A channel created at IMPORTANCE_LOW/IMPORTANCE_MIN, or IMPORTANCE_DEFAULT with no sound, alerts silently — confirm the channel is IMPORTANCE_HIGH (or DEFAULT with sound) for time-sensitive reminders.
  • On Android 12+ where exact timing matters, confirm the app holds SCHEDULE_EXACT_ALARM / USE_EXACT_ALARM (or has degraded gracefully to inexact) — an inexact alarm can fire minutes-to-hours late, which reads as "notification didn't fire."
  • Note: channel importance is set ONCE at channel creation and can't be raised in code afterward — if it's wrong, the fix requires a new channel id.

Tap Handling (live)

  • Tap the FIRED notification in the shade via mobile_click_on_screen_at_coordinates and confirm it routes to the correct screen with the right context (the task/reminder it referred to), honoring the data payload.
  • Test from background (app warm) AND from cold start (kill the app via mobile_terminate_app first, then fire + tap) — cold-start deep-link handling (getInitialNotification / lastNotificationResponse) is a separate path that commonly drops routing and lands on Home.
  • Pull mobile_list_crashes after a cold-start tap.

Cancellation & Staleness (live)

  • Schedule a notification tied to a task, then complete or delete the task in the app.
  • Confirm the scheduled notification was cancelled and does NOT still fire (advance/wait past its time and confirm silence).
  • Reschedule test: edit a reminder's time and confirm exactly ONE fires at the new time — not a duplicate where the old schedule fires too (orphaned identifier not cancelled before re-scheduling).

Local-Notif Defect Log Schema — one row per confirmed issue:

  • Scenario: basic / timezone-DST / reboot / 64-limit / channel-importance / tap-routing / cancellation.
  • Platform / OS: iOS or Android + version; simulator/emulator vs device.
  • Scheduled time vs fired time: intended local time → actual fire time (or "never fired"); note the drift/offset.
  • Content correctness: title/body/data correct at fire time? (templated content can go stale.)
  • Tap destination: where it routed (correct screen / wrong / Home on cold start).
  • Evidence: saved screenshot filenames (schedule confirmation, clock/timezone state, fired-in-shade, post-tap) + mobile_get_crash trace + recording timestamp.
  • Severity / Confidence / Fix: see Calibration; fix names the expo-notifications / notifee API + the trigger/channel/boot change.

Calibration

Severity

  • Critical: a scheduled reminder NEVER fires; it fires at the WRONG time after a DST/timezone change (precomputed absolute Date instead of a local calendar trigger); ALL scheduled notifications are lost after a reboot on Android (no boot receiver); notifications are silently dropped past the iOS 64-pending limit so reminders the user expects never arrive.
  • High: an Android channel ships silent (no heads-up/sound) so time-sensitive reminders are missed; a stale notification keeps firing for a completed/deleted task; tap handling drops routing on cold start and lands on Home; inexact Android alarm fires far enough off to be effectively missed; naive over-scheduling that will breach the 64 limit in normal use.
  • Medium: minor timing drift (seconds-to-a-minute) on a non-time-critical reminder; a duplicate notification when rescheduling (old + new both fire); foreground presentation swallows a notification that should banner.
  • Low: content/copy polish, an icon/sound preference, a cosmetically off but functionally correct fire.

Confidence

  • Confirmed: you scheduled it on-device and OBSERVED it fire (after advancing the clock / changing timezone / rebooting / waiting), screenshotted in the shade.
  • Likely: observed once with clear steps but not re-run, or inferred the timezone/reboot behavior from one path without fully re-testing the boundary.
  • Speculative: something looked off but you could not advance the clock / reach the state to confirm the fire — say so explicitly.

Anti-hallucination guard: You must ACTUALLY schedule the notification AND observe it fire — by advancing the clock, changing the timezone, rebooting, or waiting a real interval — not infer from code that it "would" fire. DST and reboot bugs are invisible to code reading and require actually changing the clock/timezone and rebooting on-device; a claim about either is Speculative unless you crossed the boundary and watched the result. If you could not advance the clock for a calendar-trigger case and substituted a short real wait instead, say so — a 60-second wait does NOT prove the daily-at-9am or the DST path. Screenshot the fired notification IN THE SHADE as evidence; a claim of "it fired" without the shade screenshot is Speculative. Always note the platform — iOS persists scheduled notifications across reboot and recomputes calendar triggers in local time but caps pending at 64; Android must reschedule on boot, gates alerting on channel importance, and needs exact-alarm permission on 12+ — a behavior correct on one platform may be a defect on the other. Label simulator/emulator caveats (clock-control flakiness, no real reboot persistence guarantees on some emulators) as environment notes, not product defects.

Output Format

Open with a 5–8 line executive summary: scenarios tested / total, how many fires were missed or mistimed, count of Critical and High, and the exact build + devices/OS tested.

Then a Risk Table — top notification-reliability risks ranked by user impact (e.g. "daily reminder drifts 3h after the user flies east → users miss the thing the app exists to remind them of → churn").

Then a Scheduling Results Table — one row per scenario:

Scenario iOS Android Fired correctly?
Near-term one-off
Timezone / DST
Reboot persistence
iOS 64-pending limit n/a
Android channel importance n/a
Tap routing (bg + cold)
Cancellation / staleness

(Use correct / ⚠️ fired-with-caveat / missed-or-mistimed.)

Then numbered sections:

  1. Environment & Build — devices, OS versions, simulator vs emulator, debug vs release, build/commit id, bundle-id/package, udid/serial, the time-control method used per scenario, recording artifact.
  2. Basic Scheduling Findings — on-time fire + content correctness, foreground vs background path.
  3. Timezone & DST Findings — did local-time reminders survive the timezone/DST change; calendar trigger vs precomputed Date.
  4. Reboot Persistence Findings — survived on iOS; rescheduled on Android (boot receiver present?).
  5. iOS 64-Pending Limit Findings — anything silently dropped; pruning/coalescing strategy.
  6. Android Channel Importance Findings — heads-up + sound vs silent; exact-alarm permission on 12+.
  7. Tap Handling Findings — routing from background and cold start.
  8. Cancellation & Staleness Findings — stale fires, duplicates on reschedule.
  9. Local-Notif Defect Log — table using the schema above.
  10. Untested Surface — scenarios you could not exercise (couldn't advance clock, no real reboot on this emulator, server-gated content) and why.

Close with a Prioritized Fix List: top items ranked by impact / effort, each naming the scenario, the platform, the broken behavior, and the smallest fix (the exact expo-notifications / notifee API + trigger type, channel importance, exact-alarm permission, or boot-reschedule change) that resolves it.

Need help applying this to a real product?

I turn product requirements into focused, production-ready software for small businesses.