Skip to content

[Bug]: Live Activity update token is never re-uploaded after a failed first attempt until the next app launch (no foreground re-drive, same-token enter dropped) #1769

Open

Description

@nikolllay

What happened?

A Live Activity started via push-to-start stays on its initial content state for the rest of the session: every update notification the server sends for that activity_id reports "No recipients", because the per-activity update token was never bound server-side. Opening the app does not repair it. The activity only recovers after the app is cold-started (or after the push subscription id changes).

Reading the Live Activities module (byte-identical in 5.5.5, 5.7.0 and 5.8.0, verified with diff -r between the tags), the per-activity update token is uploaded exactly once and nothing re-drives a failed upload while the app keeps running:

  1. The upload is a single attempt with no background execution. OneSignalLiveActivitiesManagerImpl.listenForActivityPushToUpdate calls enter as soon as ActivityKit yields the token (OneSignalLiveActivitiesManagerImpl.swift:313-328); OSLiveActivitiesExecutor.append executes it immediately (OSLiveActivitiesExecutor.swift:175-187) over a default NSURLSession (OneSignalClient.m:53-70). No beginBackgroundTask is held anywhere in the Live Activities path, although push-to-start delivers the token during a short background wake, so the request is routinely torn by suspension. Other SDK paths (OSOperationRepo, the user executors) do use OSBackgroundTaskManager; this one does not.
  2. A retryable failure is retained but never retried in-process. executeRequest's failure block keeps the request for any status other than 400/401/402/403/404/409/410 and the comment says it "will retry the next time the app starts" (OSLiveActivitiesExecutor.swift:241-253, OSNetworkingUtils.m:41-53). OneSignalClient re-attempts status 0 / 5xx up to four times at 5/15/45/135 s, but on in-memory performSelector:afterDelay: main-queue timers (OneSignalClient.m:150-176) that only advance while the process runs; once they are exhausted, or on a code it never re-attempts (429 from the per-app rate limit, or a 5xx/0 with a non-JSON body, which fails before the re-attempt check at OneSignalClient.m:204-220), the entry is dead until the next launch.
  3. The only re-drives are the one-shot launch poll and a subscription-id change. pollPendingRequests is a single asyncAfter(30 s) (OSLiveActivitiesExecutor.swift:189-202) armed from start() (:152-158), reached only from +init (OneSignal.m, startLiveActivitiesManager), and from onPushSubscriptionDidChange (:160-173). There is no foreground, new-session, reachability or periodic flush; OneSignal.m still carries // [self executePendingLiveActivityUpdates]; commented out in the on-session path.
  4. A repeated enter with the same token is dropped, whether the cached entry is pending or successful (OSRequestSetUpdateToken.swift:65-75 returns false for an identical token; append logs "superseded request not saved/executed"). So an app cannot work around the gap by calling enter again on foreground.
  5. onPushSubscriptionDidChange re-marks only start tokens. The caches { _ in self.startTokens.markAllUnsuccessful() } closure ignores its argument (OSLiveActivitiesExecutor.swift:166-171), so a successful update-token binding is never re-sent under the new subscription id; combined with (4) that binding is sticky until the 24 h cache TTL or a token rotation.

Environment: React Native app via react-native-onesignal 5.5.14 (CocoaPods, OneSignalXCFramework 5.7.0; the affected production build ran 5.5.5), Objective-C++ app delegate, setupDefault() called natively from didFinishLaunchingWithOptions (the wrapper initializes the SDK natively before the delegate body, so OneSignal.initialize has run). Activities are started and updated server-side through the REST API (activity_id addressing).

Steps to reproduce?

Deterministic from source, using Airplane Mode to make the single upload fail:

  1. Debug build with OneSignal.Debug.setLogLevel(.LL_VERBOSE); iOS 18+ device; OneSignal.LiveActivities.setupDefault() in use.
  2. Start a Live Activity (push-to-start from the server, or startDefault) and switch Airplane Mode on right after it appears, before the token upload completes. Log: OneSignal.LiveActivities executing request: (OSRequestSetUpdateToken) ... followed by request failed with error ... code 0, then the four re-attempts over ~200 s while the app stays in the foreground.
  3. After the re-attempts are exhausted, switch Airplane Mode off and background/foreground the app several times.
  4. Send an update to the activity from the dashboard or REST API: it reports No recipients. The log shows no new executing request on any foreground; a second enter for the same token logs superseded request not saved/executed.
  5. Kill and relaunch the app: ~30 s later executing outstanding requests runs, the token is bound, and the next update is delivered.

The same end state is reached whenever the first upload does not land during the push-to-start background wake (the process is suspended mid-request) or the endpoint answers 429.

What did you expect to happen?

  • A failed or unprepared update-token upload is re-driven while the app is running: at least on foreground / new session (the commented-out executePendingLiveActivityUpdates hook), and ideally with the poll re-armed after a retryable failure instead of waiting for the next launch.
  • The upload made during the push-to-start wake is protected by a bounded UIApplication background task, like the SDK's other executors.
  • 429 is honoured (Retry-After or backoff) rather than retained without any re-attempt.
  • onPushSubscriptionDidChange re-sends update tokens too, so a binding made under a previous subscription id is refreshed.

Workaround we ship meanwhile (public API only): on didBecomeActive, when an activity is running, call enter(activityId, withToken:) for each Activity<DefaultLiveActivityAttributes> (covers the 4xx-evicted case) and then OneSignalLiveActivitiesManagerImpl.start(), which re-arms the 30 s poll over every unsuccessful cached request, plus a background task around the push-to-start wake. It cannot address point 5.

OneSignal iOS SDK version

5.5.5 (affected production build), 5.7.0 (current), module unchanged in 5.8.0 (f5e24a8a and 4f9dc75f compared)

iOS version

iOS 18, iOS 26

Specific iOS version

iOS 26.x (iPhone 17 Pro, production occurrence); the code path is version-independent.

Relevant log output

Server side for the affected activity: 14 consecutive update notifications over 14 minutes with "No recipients", then "Sent (1)" on the first update after a long foreground. App logs for the incident device were not captured; the SDK's own verbose lines that reproduce the state are OneSignal.LiveActivities request failed with error (code 0), Re-scheduling request (OSRequestSetUpdateToken) to be re-attempted in 5.000 seconds x4, then OneSignal.LiveActivities superseded request not saved/executed on any later enter, and nothing until OneSignal.LiveActivities executing outstanding requests after a relaunch.

Code of Conduct

  • I agree to follow this project's Code of Conduct

Activity

  1. self-assigned this
    on Oct 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions