diff --git a/macos/Sources/Ghostty/Surface View/SurfaceView_AppKit.swift b/macos/Sources/Ghostty/Surface View/SurfaceView_AppKit.swift index 35b99e436..6af4ed1f1 100644 --- a/macos/Sources/Ghostty/Surface View/SurfaceView_AppKit.swift +++ b/macos/Sources/Ghostty/Surface View/SurfaceView_AppKit.swift @@ -1787,20 +1787,31 @@ extension Ghostty { // Note the callback may be executed on a background thread as documented // so we need @MainActor since we're reading/writing view state. - Task { @MainActor in + // We use [weak self] here because we don't want to extend the surface's + // lifetime when a notification is triggered right before the surface closes. + Task { @MainActor [weak self] in do { try await UNUserNotificationCenter.current().add(request) + guard let focused = self?.focused else { + // We remove the notification if the surface is deallocated. + UNUserNotificationCenter.current() + .removeDeliveredNotifications(withIdentifiers: [uuid]) + return + } + // We need to keep track of this notification so we can remove it // under certain circumstances - notificationIdentifiers.insert(uuid) + self?.notificationIdentifiers.insert(uuid) // If we're focused then we schedule to remove the notification // after a few seconds. If we gain focus we automatically remove it // in focusDidChange. - if self.focused { - try await Task.sleep(for: .seconds(3)) - notificationIdentifiers.remove(uuid) + if focused { + // If the suspension is failed, we remove the notification anyway. + try? await Task.sleep(for: .seconds(3)) + self?.notificationIdentifiers.remove(uuid) + // We remove the notification if the surface is deallocated while we wait. UNUserNotificationCenter.current() .removeDeliveredNotifications(withIdentifiers: [uuid]) }