Skip to content

[Bug]: Android SDK 5.9.9: invalid create-subscription pauses the operation queue across restarts #2747

Open

Description

@nowisyes

What happened?

An operation group login-user + create-subscription was rejected with HTTP 400 ("Subscription can't change notification_types to zero or negative integer while enabled"), the SDK paused its persistent operation repository, and the same invalid group was retried after every restart and after notification permission was granted. The signed-in user never became addressable by external ID. Full sanitized report below.

Steps to reproduce?

See "Application sequence and reproduction steps" below (field reproduction: notifications disabled, app data cleared, restarts, new account, later permission grant).

What did you expect to happen?

Missing permission, a missing remote alias, an initial logout or a later login should not leave a persisted operation group that can never progress; after granting permission the user should be created/associated.

OneSignal Android SDK version

5.9.9 (via react-native-onesignal 5.5.9)

Android version

13

Specific Android models

Samsung SM-T970

Relevant log output

Sanitized timeline included below; sanitized logcat excerpt available on request.

Code of Conduct

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

Environment

  • Catitin, React Native app, package com.catitin.app.
  • Samsung SM-T970, Android 13 / API 33, pilot APK build 11.
  • react-native-onesignal 5.5.9; its Android dependency is OneSignal 5.9.9.
  • Observed on 15 September 2026, local time Europe/Madrid (UTC+02:00).
  • The existing pilot APK downloaded a JavaScript update at 01:33:14; it was applied on a subsequent restart. The APK/native SDK did not change.

Observed failure

A user update returned HTTP 404 for a missing onesignal_id alias. A subsequent operation group containing login-user and create-subscription returned HTTP 400:

Subscription can't change notification_types to zero or negative integer while enabled

The SDK logged that it was pausing the operation repository. The same rejection and pause recurred after process death and restart. The new Catitin profile could not subsequently be found by its external ID, according to a separate backend inspection. Granting notification permission later did not restore that profile's association in the observed session.

This is a field reproduction in our application, not yet a standalone SDK reproducer. We have not established which SDK operation originally created the inconsistent subscription state.

Application sequence and reproduction steps

The application version tested behaved as follows:

  1. Initialize OneSignal at startup without requesting notification permission.
  2. Start an auth bridge. If restored auth confirms that no session exists, call OneSignal.logout() once to release any previous account. A temporary missing profile does not cause logout.
  3. Once a signed-in person's application profile loads, call OneSignal.login(profileId) without first checking notification permission. Language and tags were also written separately.
  4. After new account registration, show an application explanation sheet. Its affirmative action calls OneSignal.Notifications.requestPermission(false). The notification settings screen can instead call it with true to allow the SDK's settings fallback.

The tester performed this sequence on the pilot APK:

  1. Disable Catitin notifications in Android Settings, then clear Catitin application data.
  2. Delete and recreate a disposable application account. This deletes our application account; we have not confirmed whether the server-side OneSignal user was deleted. Do not assume a clean OneSignal backend.
  3. Launch Catitin, allow the pilot JavaScript update to download, remove the task and reopen to apply it. There were further task removals/reopens in this run; see the timestamps below.
  4. Complete registration. The explanation sheet appears before entering the room.
  5. Leave the app with the sheet open, then return. The tested application had a separate sheet lifecycle defect; the tester eventually requested notifications from Catitin's notification settings screen.
  6. Grant notification permission and return to Catitin. Inspect the operation queue and the application's external-ID association.

For an independent SDK reproduction, preserve the initial no-session logout(), the missing-permission startup and the restarts as well as the later profile login(). Testing only login(newProfile) would omit an earlier failure already visible in this trace.

Sanitized timeline

All times below are local on 15 September 2026. Line references point to our internal sanitized logcat excerpt, which we can share on request.

Time Observation
01:33:12 Application process starts after data reset.
01:33:14 Pilot JavaScript update download completes.
01:33:20 / 01:33:23 Process killed for remove task, then restarted.
01:33:35.037 PATCH 404: no user with the requested onesignal_id alias (line 716).
01:33:35.342–343 POST 400 above; grouped login-user + create-subscription; operation repository paused (717–718).
01:33:44 / 01:33:48 Another task removal and new process.
01:33:53.912 Same HTTP 400 and queue pause (737–738).
01:34:19 New application profile created, according to the backend inspection: 23:34:19 UTC on 14 September.
01:39:06 / 01:39:08 Process killed for remove task, then restarted.
01:39:08.836 Same HTTP 400 and queue pause (765–766). A separate session-metric request also rejects an unresolved local ID (767).
01:39:35–38 OneSignal PermissionsActivity, then Android GrantPermissionsActivity; return to MainActivity.
01:47:20 A later inspection reported another invalid retry through SyncJobService; this later sample is described in our diagnostic report but is not included in the supplied raw excerpt.

Limits of the evidence

  • The first 400 precedes creation of the new application profile by about 44 seconds. We cannot attribute that first queued failure to login(newProfileId).
  • The visible, complete login-user object in the first failed group has no externalId field. The create-subscription part shows type:PUSH and enabled:true, but that raw line is truncated. Its address is intentionally omitted here.
  • A later diagnostic inspection reports status:NO_PERMISSION alongside enabled:true; the initial truncated raw line alone does not establish that status field. The server's 400 does establish that the requested permission/enabled state was invalid.
  • The later external-ID lookup returning 404 and the 01:47:20 retry are reported by our separate diagnostic inspection, not independently re-queried while preparing this report.
  • An earlier 409 in the original trace belongs to another account before the reset. We do not identify it as the cause of this episode.
  • No email address, device push token, credential, OneSignal App ID, or real profile/user/subscription UUID is included in this report. Please request a suitably redacted diagnostic export if more queue data is needed.

Expected behavior and questions

A missing notification permission, a missing remote alias, an initial logout, or a later login should not leave a persisted operation group unable to progress after permission is granted. Please help identify the path generating the inconsistent create-subscription and a supported recovery for an already persisted invalid group.

PR #2627 documents a related subscription-state issue and explicitly leaves a sibling LoginUserOperationExecutor.createSubscriptionsFromOperation path outside its change. That makes it relevant to investigate; we have not proven this trace uses the same root cause. PR #2636 excludes NO_PERMISSION from its subscription self-healing. Queue behavior can be examined in OperationRepo at 5.9.9.

Application mitigation and follow-up field test

We deployed an application change (pilot channel, same APK/native SDK 5.9.9) that calls OneSignal.login(profileId) only after getPermissionAsync() returns true (at startup, after requestPermission resolves true, on permissionChange, and when the app returns to foreground), and never calls logout() for a merely missing permission. The confirmed no-session logout() remains.

Follow-up test on the same tablet the next morning, starting again from cleared app data with notifications disabled:

  1. Two launches without signing in and without permission: no HTTP 400 and no "pausing the operation repo".
  2. New account, explanation sheet answered "Not now": no login-user attempt, no errors.
  3. Permission enabled in Android Settings, then returning to the app: the profile became addressable by external ID with one enabled push subscription, and a test notification was delivered.
  4. Sign-out and sign-in with a different account: the subscription moved to the new user (expected 409 during login), no queue pause.

So deferring login() until permission is granted avoided the failure in our tests. It does not purge an already invalid persisted queue, and we still do not know which operation created the first invalid group in the original trace. We think the SDK should not be able to persist a create-subscription that the backend always rejects, nor keep retrying it after permission is granted.

Activity

  1. nan-li commented on Sep 28, 2026

    @nan-li
    Contributor

    Hi @nowisyes thanks for the detailed reporting.

    The issue you ran into is corrected in Android SDK 5.10.1 by #2728 (react-native-onesignal version 5.5.12 and later). Using that release prevents new occurrences. Separately, we are working on fixes to recover those devices that run into this issue and get stuck.

    On your mitigation: deferring login() until permission is granted avoids the trigger, but it also means users who decline notifications are not identified by external ID, which affects the user's data syncing. Once you are on react-native-onesignal version 5.5.12 or later you should be able to call login() as soon as the profile loads again. Calling logout() with no user logged in is a no-op and did not contribute.

  2. antoinedelp commented on Oct 27, 2026

    @antoinedelp

    Hi @nan-li, thanks for the follow-up on this issue. We are hitting the same failure in production and would like to know where the recovery fix stands.

    Environment

    • React Native app, react-native-onesignal 5.5.14, so OneSignal Android SDK 5.10.2 (iOS SDK 5.7.0 works fine)
    • Android only: Samsung, Xiaomi, vivo, HONOR, Android 11 to 16
    • login() is called once notification permission is granted, the same sequence as the original report

    What we observe
    Over the last 6 days, 407 Android users (4,461 occurrences) end up in this state, 15 seconds after login():

    Field | Value -- | -- getOnesignalId() | null getExternalId() | our user id (set) pushSubscription.getIdAsync() | null pushSubscription.getTokenAsync() | FCM token present pushSubscription.getOptedInAsync() | true Notifications.getPermissionAsync() | true

    The same users report it again on every app launch, so the state persists across restarts. This matches the paused, persisted operation queue described above.

    On a later launch the SDK already holds our external id. LoginHelper then treats login() with the same id as a no-op, so nothing we call from JS gets the user created again.

    Questions

    1. Is a fix to recover devices that are already stuck planned, and is there an ETA? Will it ship in a 5.10.x patch picked up by react-native-onesignal?
    2. Until then, is there a supported way to clear or unblock the persisted operation queue from the app side? For example, does logout() followed by login() reach the backend while the queue is paused, or does the invalid group stay at the head?
    3. You recommended calling login() as soon as the profile loads on 5.5.12+. Is that enough to prevent new occurrences on 5.10.2? Fresh installs on 5.10.2 are affected. Among accounts created since Oct 22 (all installed with SDK 5.10.2, never on an older version), 23 of 120 Android users who granted push permission (19%) end up stuck in the state above, versus 1 of 119 on iOS. So 5.10.1+ does not prevent new occurrences for us.

    We can share sanitized diagnostics and the affected external ids privately with support if that helps.

    Thanks!


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions