Skip to content

fix: prevent stale FetchUser from clobbering the current user's identity#1672

Merged
nan-li merged 1 commit into
mainfrom
nan/fix-fetchuser-clobbers-login
Jun 9, 2026
Merged

fix: prevent stale FetchUser from clobbering the current user's identity#1672
nan-li merged 1 commit into
mainfrom
nan/fix-fetchuser-clobbers-login

Conversation

@nan-li

@nan-li nan-li commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Description

One Line Summary

Stop a stale Fetch User call from clearing the current user's data, which was dropping the external_id set by login() and orphaning subsequent properties updates on that user.

Details

Motivation

Reported by a customer who added the SDK in an app update, then called login + addEmail, and sometimes the emails landed on the wrong anonymous user and the login silently failed.

Root cause is an ordering race in OSUserExecutor: when an on-new-session OSRequestFetchUser for a previous user (a cached anonymous user) is still pending and login() switches the current user, the in-flight Fetch User's success handler called clearUserData() unconditionally, wiping the external_id that login() had just set on the new current user. The following user-2 (409) conflict recovery then built an OSRequestCreateUser with external_id: nil, minting a stray anonymous user and dropping the login.

#1669 (the prewarm fix) makes this reproduction now very hard to hit (by deferring user creation until after first unlock it restores the normal launch ordering where no on-new-session Fetch User races ahead of login) but it does address the root issue. The stale-fetch clobber still occurs any time a Fetch User is pending ahead of a login()-driven request (e.g. a programmatic login() right after initialize, or slow network), no prewarm required.

Scope

  • executeFetchUserRequest now early-returns when the fetched user is no longer the current user, which is safe, a stale response is discarded rather than clearing/hydrating the now-current user.
  • parseFetchUserResponse is unchanged, as it is also used by the Create User path that remains unaffected.
  • No public API changes.

Testing

Unit testing

Added two regression tests in UserExecutorTests:

  • testFetchUser_forNonCurrentUser_doesNotClearCurrentUserData — a stale fetch must not clear the current user (fails before this fix, passes after).
  • testFetchUser_forCurrentUser_stillClearsStaleData — the normal current-user fetch still clears + hydrates.

Full UserExecutorTests and SwitchUserIntegrationTests pass.

Manual testing

The original bug was captured in repro logs (FetchUser running ahead of IdentifyUser → CreateUser with external_id: nil). The race is now reproduced deterministically by the unit test above; reproducing the prewarm-triggered timing reliably on-device is impractical.

Affected code checklist

  • Notifications
    • Display
    • Open
    • Push Processing
    • Confirm Deliveries
  • Outcomes
  • Sessions
  • In-App Messaging
  • REST API requests
  • Public API changes

Checklist

Overview

  • I have filled out all REQUIRED sections above
  • PR does one thing
  • Any Public API changes are explained in the PR details and conform to existing APIs

Testing

  • I have included test coverage for these changes, or explained why they are not needed
  • All automated tests pass, or I explained why that is not possible
  • I have personally tested this on my device, or explained why that is not possible

Final pass

  • Code is as readable as possible.
  • I have reviewed this PR myself, ensuring it meets each checklist item

🤖 Generated with Claude Code

When an on-new-session Fetch User for a previous user (e.g. a cached
anonymous user) is still pending and login() switches the current user,
the in-flight Fetch User completing cleared and re-hydrated local data
unconditionally — wiping the external_id login had just set on the new
current user. The subsequent user-2 (409) conflict recovery then built a
Create User with external_id: nil, minting a stray anonymous user and
silently dropping the login (and any addEmail/tags applied afterward).

In executeFetchUserRequest, early-return when the fetched user is no
longer the current user: such a response is stale, so clearing/hydrating
local data would only clobber the now-current user. Add regression tests
for both the non-current (must not touch current user) and current (must
still clear + hydrate) cases.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@nan-li nan-li requested a review from a team June 9, 2026 14:59
@nan-li nan-li merged commit 80ccd53 into main Jun 9, 2026
2 of 4 checks passed
@nan-li nan-li deleted the nan/fix-fetchuser-clobbers-login branch June 9, 2026 19:43
@github-actions github-actions Bot mentioned this pull request Jun 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants