From 72ffe2fab54fd100c67f2de54866edff40c69494 Mon Sep 17 00:00:00 2001 From: Anna Garcia Date: Thu, 29 Oct 2026 17:20:31 -0400 Subject: [PATCH 1/3] fix(replay): start a new session on opt-in whenever replay was wanted --- posthog/src/main/java/com/posthog/PostHog.kt | 20 ++++++++-- .../src/test/java/com/posthog/PostHogTest.kt | 40 +++++++++++++++++++ 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/posthog/src/main/java/com/posthog/PostHog.kt b/posthog/src/main/java/com/posthog/PostHog.kt index 281503ecb..6b35d791a 100644 --- a/posthog/src/main/java/com/posthog/PostHog.kt +++ b/posthog/src/main/java/com/posthog/PostHog.kt @@ -458,7 +458,8 @@ public class PostHog private constructor( sessionReplayHandler = it if (startSessionReplay) { - startSessionReplayInternal(resumeCurrent = resumeSessionReplay) + // setup() resumes the current session and optIn() has already started a new one + startSessionReplayInternal(resumeCurrent = resumeSessionReplay, rotateSession = false) } } else if (it is PostHogSurveysHandler) { surveysHandler = it @@ -1364,6 +1365,12 @@ public class PostHog private constructor( } if (wasOptedOut) { + // rotate even if the linked flag or remote config then holds replay off, so a later start + // doesn't resume the session that was active while opted out + if (sessionReplayWanted) { + PostHogSessionManager.endSession() + PostHogSessionManager.startSession() + } installIntegrations(resumeSessionReplay = false, startSessionReplay = sessionReplayWanted, surveys = surveys) // install re-arms each integration from local config; re-apply the cached remote // config (e.g. exception autocapture turned off remotely) instead of waiting for a reload @@ -2527,7 +2534,10 @@ public class PostHog private constructor( } } - private fun startSessionReplayInternal(resumeCurrent: Boolean) { + private fun startSessionReplayInternal( + resumeCurrent: Boolean, + rotateSession: Boolean = !resumeCurrent, + ) { if (!isEnabled()) { return } @@ -2552,8 +2562,10 @@ public class PostHog private constructor( it.start(true) } else { - endSession() - startSession() + if (rotateSession) { + endSession() + startSession() + } if (!shouldRecordSession()) { return diff --git a/posthog/src/test/java/com/posthog/PostHogTest.kt b/posthog/src/test/java/com/posthog/PostHogTest.kt index 01286caba..10d06b33c 100644 --- a/posthog/src/test/java/com/posthog/PostHogTest.kt +++ b/posthog/src/test/java/com/posthog/PostHogTest.kt @@ -1508,6 +1508,46 @@ internal class PostHogTest { sut.close() } + @Test + fun `optIn starts a new session when replay is wanted but its flag holds it off`() { + val http = mockHttp() + val url = http.url("/") + val integration = PostHogSessionReplayHandlerFake(false) + + val sut = getSut(url.toString(), sessionReplay = true, preloadFeatureFlags = false, integration = integration) + val sessionAtOptOut = sut.getSessionId() + + sut.optOut() + integration.reset() + sut.optIn() + + assertFalse(integration.startCalled) + assertNotEquals(sessionAtOptOut, sut.getSessionId()) + + sut.close() + } + + @Test + fun `optIn keeps the session when the app stopped replay before opting out`() { + val http = mockHttp() + val url = http.url("/") + val integration = PostHogSessionReplayHandlerFake(false) + val myPrefs = PostHogMemoryPreferences() + myPrefs.setValue(SESSION_REPLAY, emptyMap()) + + val sut = + getSut(url.toString(), sessionReplay = true, cachePreferences = myPrefs, preloadFeatureFlags = false, integration = integration) + sut.stopSessionReplay() + val sessionAtOptOut = sut.getSessionId() + + sut.optOut() + sut.optIn() + + assertEquals(sessionAtOptOut, sut.getSessionId()) + + sut.close() + } + @Test fun `flags-loaded replay start can't land after a concurrent optOut`() { val http = mockHttp() From ac1b6c5b2588f2a8c993c49325ac49de68d92ebd Mon Sep 17 00:00:00 2001 From: Anna Garcia Date: Thu, 29 Oct 2026 17:32:53 -0400 Subject: [PATCH 2/3] chore: add changeset --- .changeset/opt-in-new-session-when-replay-wanted.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/opt-in-new-session-when-replay-wanted.md diff --git a/.changeset/opt-in-new-session-when-replay-wanted.md b/.changeset/opt-in-new-session-when-replay-wanted.md new file mode 100644 index 000000000..400821b15 --- /dev/null +++ b/.changeset/opt-in-new-session-when-replay-wanted.md @@ -0,0 +1,6 @@ +--- +"posthog": patch +"posthog-android": patch +--- + +Start a new session on `optIn()` whenever session replay was wanted at opt-out, even if a linked flag or unloaded remote config keeps replay from starting yet. From f00bc2e30cf514444810ae006a710191a9cee32b Mon Sep 17 00:00:00 2001 From: Anna Garcia Date: Thu, 29 Oct 2026 17:53:11 -0400 Subject: [PATCH 3/3] chore: reword changeset --- .changeset/opt-in-new-session-when-replay-wanted.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/opt-in-new-session-when-replay-wanted.md b/.changeset/opt-in-new-session-when-replay-wanted.md index 400821b15..fafbc947a 100644 --- a/.changeset/opt-in-new-session-when-replay-wanted.md +++ b/.changeset/opt-in-new-session-when-replay-wanted.md @@ -3,4 +3,4 @@ "posthog-android": patch --- -Start a new session on `optIn()` whenever session replay was wanted at opt-out, even if a linked flag or unloaded remote config keeps replay from starting yet. +Fix session replay recording into the opted-out session when a linked feature flag or pending remote config delayed its restart after `optIn()`.