Surface attribute sync errors when fetching offerings (#4129)
## Description Currently `syncAttributesAndOfferingsIfNeeded()` does not actually return an error if attribute syncing fails. Which is unexpected if you're trying to sync attributes that are expected to influence the offerings returned. This PR fixes this by returning the error returned from syncing attributes, before attempting to refresh the offerings. Ensuring that the error is surfaced correctly. ## Changes We will now treat any error received during attribute syncing as fatal, thus returning and not continuing with fetching offerings, with one exception. (Some) reserved attributes can only be set once, and any subsequent sync of an updated value will result in an error. Therefore we will treat these as non fatal, so if a sync results in errors only from reserved attributes, we'll still consider it a success and continue. This of course does not apply if any other errors are also encountered. iOS counterpart: RevenueCat/purchases-ios#7539 <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes public-facing behavior of sync-then-offerings flows: callers may now see errors that were previously swallowed, though reserved-attribute 7263-only cases remain non-fatal by design. > > **Overview** > **`syncAttributesAndOfferingsIfNeeded`** now fails the callback when subscriber attribute sync hits a blocking error, instead of always continuing to refresh offerings. > > Attribute sync reports per-request outcomes through a new optional **`syncedAttribute`** hook on **`SubscriberAttributesManager.synchronizeSubscriberAttributesForAllUsers`**. The orchestrator records the first blocking error and, after all syncs finish, either invokes **`onError`** and skips offerings, or proceeds with remote config refresh and **`getOfferings`** as before. > > Blocking is skipped only for **`InvalidSubscriberAttributesError`** (backend **7263**) when every reported attribute error is on a reserved key (name starts with **`$`**) and carries that code. Custom attributes, mixed errors, missing attribute details, other error codes (e.g. **7264**), and non-attribute failures still block offerings fetch. > > **`SubscriberAttributeError`** gains optional **`backendErrorCode`**, populated when parsing attribute error JSON in **`getAttributeErrors`** so the reserved-only exception can be applied reliably. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 672ece840ce7df0792a3ecb45c90ba1a1bf65820. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
R
Rick committed
0a50b9a773d71a6699549429dc9f71ccc57cb718
Parent: f960c31
Committed by GitHub <noreply@github.com>
on 8/28/2026, 3:34:38 PM