Skip to content

Bug: Resolve feature flags for the member, not anonymously, from the first fetch - #3113

Draft
panasetskaya wants to merge 1 commit into
developfrom
bug/feature-flag-member-stickiness
Draft

Bug: Resolve feature flags for the member, not anonymously, from the first fetch#3113
panasetskaya wants to merge 1 commit into
developfrom
bug/feature-flag-member-stickiness

Conversation

@panasetskaya

Copy link
Copy Markdown
Contributor

Problem

The analytics-consent step appeared in onboarding on a build where disable_analytics was ON at 100% in production.

Root cause

Unleash evaluates strategies server-side against the context of the fetch. HedvigUnleashClient constructed DefaultUnleash with memberId = null, started it, and only switched context once MemberIdService emitted — so the first fetch of every launch was anonymous.

disable_analytics used memberId stickiness. Flexible rollout resolves the stickiness field out of the context before applying the percentage and returns false when it's absent, so at 100% the flag still came back off. Confirmed against the frontend API: with the app's startup context the toggle is absent from the response; add properties[memberId] and it comes back enabled.

The SDK then raised isReady() on that anonymous set, so awaitReady() was satisfied by it. neverFetchedDefaults didn't help — it only applies while !isReady(), and an absent toggle in a delivered set correctly reads false, since that's how Unleash expresses "off".

OnboardingGate awaited readiness, read the kill switch as off, and OnboardingSessionStore pinned the resulting path for the whole flow, so the later correct value couldn't remove the step.

Changes

  • Read the first member id before client.start(), so the first fetch names them. This also makes the SDK's on-disk backup usable again — it's keyed by context.hashCode(), which the anonymous startup context never matched.
  • awaitReady() now additionally requires that the toggles in hand were fetched for the context currently in effect. Seeding can't cover a login inside a running process, which is exactly when OnboardingGate asks. It had one caller, so the iOS/JVM/fake implementations are untouched.
  • Set userId alongside the memberId property. default stickiness resolves userId → sessionId → random, and the app sent neither, so any rollout under 100% was re-diced on every 2s poll.
  • A member id read that throws now degrades to starting anonymously, instead of leaving Unleash unstarted for the whole session.
  • valueOf() is deliberately unchanged: gating it on member-match would push every flag to its default during the post-login window.

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.

1 participant