From 302c9fdd7ec3e33ffd95adcea1b9f1717a317882 Mon Sep 17 00:00:00 2001 From: Anna Garcia Date: Tue, 2 Jun 2026 16:01:15 -0400 Subject: [PATCH 1/2] fix: always invoke reloadFeatureFlags callback The reloadFeatureFlags(onFeatureFlags) early-returns (SDK disabled/opted-out, blank distinctId) skipped the completion callback, so callers awaiting it (e.g. the Flutter SDK) could hang. Invoke the callback before returning. Co-Authored-By: Claude Opus 4.8 (1M context) --- .changeset/reload-flags-callback-contract.md | 5 +++++ posthog/src/main/java/com/posthog/PostHog.kt | 4 ++++ 2 files changed, 9 insertions(+) create mode 100644 .changeset/reload-flags-callback-contract.md diff --git a/.changeset/reload-flags-callback-contract.md b/.changeset/reload-flags-callback-contract.md new file mode 100644 index 000000000..994be909a --- /dev/null +++ b/.changeset/reload-flags-callback-contract.md @@ -0,0 +1,5 @@ +--- +"posthog": patch +--- + +`reloadFeatureFlags` now always invokes its completion callback, including when the SDK is disabled/opted-out or the distinct ID is blank. Previously these early-returns skipped the callback, which could leave callers that await it (e.g. the Flutter SDK's `reloadFeatureFlags`) hanging indefinitely. diff --git a/posthog/src/main/java/com/posthog/PostHog.kt b/posthog/src/main/java/com/posthog/PostHog.kt index 2980c3217..2d9b963f0 100644 --- a/posthog/src/main/java/com/posthog/PostHog.kt +++ b/posthog/src/main/java/com/posthog/PostHog.kt @@ -1251,6 +1251,8 @@ public class PostHog private constructor( public override fun reloadFeatureFlags(onFeatureFlags: PostHogOnFeatureFlags?) { if (!isEnabled()) { + // Still invoke the callback so awaiting callers aren't left hanging. + onFeatureFlags?.loaded() return } loadFeatureFlagsRequest( @@ -1275,6 +1277,8 @@ public class PostHog private constructor( if (distinctId.isBlank()) { config?.logger?.log("Feature flags not loaded, distinctId is invalid: $distinctId") + // Still invoke the callback so awaiting callers aren't left hanging. + onFeatureFlags?.loaded() return } From acf400cae0e373eadfb1866fbc1ec3af0ded2d62 Mon Sep 17 00:00:00 2001 From: Anna Garcia Date: Tue, 2 Jun 2026 17:31:33 -0400 Subject: [PATCH 2/2] fix: guard reloadFeatureFlags callback against exceptions + add test Route the early-return callback invocations through a try/catch helper so a throwing caller callback can't escape reloadFeatureFlags (matching runOnFeatureFlagsCallbacks). Add a test asserting the callback fires when the SDK is not enabled. Co-Authored-By: Claude Opus 4.8 (1M context) --- posthog/src/main/java/com/posthog/PostHog.kt | 13 +++++++++++-- .../java/com/posthog/PostHogFeatureFlagsTest.kt | 16 ++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/posthog/src/main/java/com/posthog/PostHog.kt b/posthog/src/main/java/com/posthog/PostHog.kt index 2d9b963f0..21d3f3c57 100644 --- a/posthog/src/main/java/com/posthog/PostHog.kt +++ b/posthog/src/main/java/com/posthog/PostHog.kt @@ -1249,10 +1249,19 @@ public class PostHog private constructor( } } + // Invokes the feature flags callback, swallowing exceptions like runOnFeatureFlagsCallbacks. + private fun notifyFeatureFlagsCallback(onFeatureFlags: PostHogOnFeatureFlags?) { + try { + onFeatureFlags?.loaded() + } catch (e: Throwable) { + config?.logger?.log("Executing the feature flags callback failed: $e") + } + } + public override fun reloadFeatureFlags(onFeatureFlags: PostHogOnFeatureFlags?) { if (!isEnabled()) { // Still invoke the callback so awaiting callers aren't left hanging. - onFeatureFlags?.loaded() + notifyFeatureFlagsCallback(onFeatureFlags) return } loadFeatureFlagsRequest( @@ -1278,7 +1287,7 @@ public class PostHog private constructor( if (distinctId.isBlank()) { config?.logger?.log("Feature flags not loaded, distinctId is invalid: $distinctId") // Still invoke the callback so awaiting callers aren't left hanging. - onFeatureFlags?.loaded() + notifyFeatureFlagsCallback(onFeatureFlags) return } diff --git a/posthog/src/test/java/com/posthog/PostHogFeatureFlagsTest.kt b/posthog/src/test/java/com/posthog/PostHogFeatureFlagsTest.kt index bf329361e..6c48db905 100644 --- a/posthog/src/test/java/com/posthog/PostHogFeatureFlagsTest.kt +++ b/posthog/src/test/java/com/posthog/PostHogFeatureFlagsTest.kt @@ -14,6 +14,7 @@ import kotlin.test.AfterTest import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNotEquals +import kotlin.test.assertTrue internal class PostHogFeatureFlagsTest { @get:Rule @@ -374,4 +375,19 @@ internal class PostHogFeatureFlagsTest { assertEquals("nonExistentFlag", theEvent.properties!!["\$feature_flag"]) sut.close() } + + @Test + fun `reloadFeatureFlags invokes callback when not enabled`() { + val http = mockHttp() + val url = http.url("/") + val sut = getSut(url.toString(), preloadFeatureFlags = false) + sut.close() + + var called = false + sut.reloadFeatureFlags { called = true } + + // isEnabled() is false after close(), so reloadFeatureFlags early-returns. The + // callback must still fire (synchronously) rather than leave awaiting callers hanging. + assertTrue(called) + } }