tauri-apps / tauri-apps/plugins-workspace

[notification] iOS: toActiveNotification force-unwraps notificationsMap, crashing on any action tap after the app was terminated

Open Beginner friendly
#3,533 0 comments 0 reactions 0 assignees View on GitHub
platform: ios plugin: notification type: bug
Dominant language
Rust
Stars
1.8k
Forks
602
Avg merge
4d 14h
Merged PRs (30d)
9

Description

### Describe the bug

`toActiveNotification` force-unwraps an in-memory dictionary that is empty in the exact case a scheduled notification is designed for:

https://github.com/tauri-apps/plugins-workspace/blob/v2/plugins/notification/ios/Sources/NotificationHandler.swift

```swift
func toActiveNotification(_ request: UNNotificationRequest) -> ActiveNotification {
let notificationRequest = notificationsMap[request.identifier]! // 💥
```

`notificationsMap` is populated only by `saveNotification`, at schedule time, **in the current process**. A notification scheduled hours earlier and delivered after iOS has terminated the app finds it empty, and the force-unwrap traps.

That is the normal case for any scheduled reminder — the whole point of `Schedule.at` is that it survives the app not running — and the only case for an action tap forwarded from a paired Apple Watch.

Both callers reach it:

- `didReceive(response:)` — user taps the notification or one of its action buttons
- `willPresent(notification:)` — notification arrives while the app is foreground

### Reproduction

1. `sendNotification` with `schedule: Schedule.at(...)` a few minutes out
2. Force-quit the app (or let iOS terminate it)
3. When the notification fires, tap it

The app relaunches and immediately crashes instead of delivering the action.

### Suggested fix

Nothing the caller needs actually requires the map. `id`, `title`, `body` and `actionTypeId` all come from the `UNNotificationRequest`, which is always present; only `sound` and `attachments` came from the map, and both degrade cleanly:

```swift
let stored = notificationsMap[request.identifier]
return ActiveNotification(
id: Int(request.identifier) ?? -1,
title: request.content.title,
body: request.content.body,
sound: stored?.sound ?? "",
actionTypeId: request.content.categoryIdentifier,
attachments: stored?.attachments
)
```

Behaviour is unchanged whenever the entry exists. The same file already uses the safe `if let` form a few lines earlier in `willPresent`, so this reads as an oversight rather than a deliberate invariant.

### Full `tauri info` output

Plugin version 2.3.3; also present on `v2` HEAD as of today. Reproduced on an iOS 26 simulator with `tauri-cli` 2.9.5.

### Stack trace

Crash on relaunch in `toActiveNotification`, via `didReceive(response:)`.

### Additional context

I have been running this as a vendored patch for a few weeks and it has been stable. Happy to open a PR if useful.

Contributor guide

Open the contributing guide

Research direction

Start in plugins/notification/ios/Sources/NotificationHandler.swift at toActiveNotification, then inspect its callers didReceive(response:) and willPresent(notification:). Verify the notification request supplies the core fields when notificationsMap is empty, and run the notification plugin’s iOS tests if available. Done means scheduled notifications opened after app termination no longer crash and stored sound or attachments remain available when present.

Written by the indexing model from the issue text.

Assessment

Tech stack
ios, swift
Domain
mobile
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
88/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.