You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The SSE reconnection support added for #52 (GBFeaturesRepository.initialize(Boolean retryOnFailure)) is never reachable from the multi-user mode GrowthBookClient: it always calls the no-arg repository.initialize(), which delegates to initialize(false), and Options exposes no way to opt in. As a result, any app using GrowthBookClient with FeatureRefreshStrategy.SERVER_SENT_EVENTS permanently stops receiving feature updates after the first abnormal SSE stream failure — silently.
Affected versions
Verified against the published sources of 0.10.6 and 0.10.10 (latest release), and against current main.
With retryOnFailure == false, an abnormal stream failure (connection reset, proxy/pod restart, mid-stream IOException) does nothing:
no reconnect attempt;
no polling fallback — schedulePolling() early-returns for SERVER_SENT_EVENTS;
no observability — this path never invokes onRefreshFailed(...), so registered FeatureRefreshCallback.onError(...) callbacks never fire.
The client then serves the last in-memory features forever (with isCacheDisabled(true) there is not even a cache to refresh from), and the application has no signal that anything is wrong.
Note that a graceful server close does reconnect on these versions (onClosed → handler.onClose → unconditional createEventSourceListenerAndStartListening), which makes the failure mode intermittent and easy to miss in testing: clean proxy restarts recover, abrupt failures don't.
#212 nicely reworked the retry machinery (scheduleSseReconnect with exponential backoff via FeatureFetchRetryPolicy, Options.retryPolicy), but the gate is still there and multi-user mode still never passes true:
GrowthBookClient.initializeFeaturesRepository(...) still calls the no-arg repositorySnapshot.initialize(), so Options.retryPolicy is configurable but the SSE reconnect it parameterizes is unreachable from multi-user mode.
There is also a behavioral tightening on main: the graceful-close path now routes through scheduleSseReconnect(retryOnFailure) as well, so once this ships, multi-user mode + SSE will no longer reconnect even on a graceful close (which 0.10.x did unconditionally). That widens this issue rather than fixing it.
Reproduction sketch
Build a GrowthBookClient (multi-user mode) with refreshStrategy = SERVER_SENT_EVENTS pointing at a GrowthBook Proxy; call initialize().
Kill the proxy abruptly (or drop the TCP connection), then bring it back.
Change a feature flag.
The client never reconnects, FeatureRefreshCallback stays silent, and evaluations keep returning the pre-failure values indefinitely.
Summary
The SSE reconnection support added for #52 (
GBFeaturesRepository.initialize(Boolean retryOnFailure)) is never reachable from the multi-user modeGrowthBookClient: it always calls the no-argrepository.initialize(), which delegates toinitialize(false), andOptionsexposes no way to opt in. As a result, any app usingGrowthBookClientwithFeatureRefreshStrategy.SERVER_SENT_EVENTSpermanently stops receiving feature updates after the first abnormal SSE stream failure — silently.Affected versions
Verified against the published sources of 0.10.6 and 0.10.10 (latest release), and against current
main.Details (0.10.6 / 0.10.10)
The chain:
GrowthBookClient.initialize()→repository.initialize()(no-arg)GBFeaturesRepository.initialize()→initialize(false)—retryOnFailurehardwired tofalsecreateEventSourceListenerAndStartListening(retryOnFailure):With
retryOnFailure == false, an abnormal stream failure (connection reset, proxy/pod restart, mid-streamIOException) does nothing:schedulePolling()early-returns forSERVER_SENT_EVENTS;onRefreshFailed(...), so registeredFeatureRefreshCallback.onError(...)callbacks never fire.The client then serves the last in-memory features forever (with
isCacheDisabled(true)there is not even a cache to refresh from), and the application has no signal that anything is wrong.Note that a graceful server close does reconnect on these versions (
onClosed→handler.onClose→ unconditionalcreateEventSourceListenerAndStartListening), which makes the failure mode intermittent and easy to miss in testing: clean proxy restarts recover, abrupt failures don't.Still present on
main(after #212)#212 nicely reworked the retry machinery (
scheduleSseReconnectwith exponential backoff viaFeatureFetchRetryPolicy,Options.retryPolicy), but the gate is still there and multi-user mode still never passestrue:GrowthBookClient.initializeFeaturesRepository(...)still calls the no-argrepositorySnapshot.initialize(), soOptions.retryPolicyis configurable but the SSE reconnect it parameterizes is unreachable from multi-user mode.There is also a behavioral tightening on
main: the graceful-close path now routes throughscheduleSseReconnect(retryOnFailure)as well, so once this ships, multi-user mode + SSE will no longer reconnect even on a graceful close (which 0.10.x did unconditionally). That widens this issue rather than fixing it.Reproduction sketch
GrowthBookClient(multi-user mode) withrefreshStrategy = SERVER_SENT_EVENTSpointing at a GrowthBook Proxy; callinitialize().FeatureRefreshCallbackstays silent, and evaluations keep returning the pre-failure values indefinitely.Suggested fix
Either of:
GrowthBookClientcallrepository.initialize(true)— with Feature: Force Refresh API and Exponential Retry #212's bounded backoff this seems like a safe default for a long-lived server-side client; orOptions(e.g.sseReconnectOnFailure) and thread it through toinitialize(...).Happy to send a PR for either direction if you have a preference.