From cfa9abc334b2b08a7b82cfac1a13641041ac74e7 Mon Sep 17 00:00:00 2001 From: "posthog[bot]" <206114724+posthog[bot]@users.noreply.github.com> Date: Tue, 1 Sep 2026 07:17:15 +0000 Subject: [PATCH 1/2] fix(browser): gate group() on person processing group() was the one identity method that skipped _requirePersonProcessing. Under person_profiles 'never' or 'identified_only' before an identify, the $groupidentify event went out stamped $process_person_profile: false, and the server dropped it. The group was never created or updated, and the SDK logged nothing. Gate group() like identify(), alias(), and setGroupPropertiesForFlags(): it now promotes the user to identified under 'identified_only' and logs an error under 'never'. Generated-By: PostHog Desktop Task-Id: ecd71523-a255-4b1d-8226-6bed53a9a671 --- .changeset/gate-group-on-person-processing.md | 5 +++++ .../src/__tests__/personProcessing.test.ts | 18 ++++++++---------- packages/browser/src/posthog-core.ts | 4 ++++ 3 files changed, 17 insertions(+), 10 deletions(-) create mode 100644 .changeset/gate-group-on-person-processing.md diff --git a/.changeset/gate-group-on-person-processing.md b/.changeset/gate-group-on-person-processing.md new file mode 100644 index 0000000000..2be96978f3 --- /dev/null +++ b/.changeset/gate-group-on-person-processing.md @@ -0,0 +1,5 @@ +--- +'posthog-js': patch +--- + +Gate `group()` on person processing, like the other identity methods. Under `identified_only` a `group()` call now promotes the user to identified, so the `$groupidentify` event is kept instead of dropped. Under `never` the call is ignored and logs an error, instead of sending an event the server always drops. diff --git a/packages/browser/src/__tests__/personProcessing.test.ts b/packages/browser/src/__tests__/personProcessing.test.ts index d891cb5d86..4ce5289f78 100644 --- a/packages/browser/src/__tests__/personProcessing.test.ts +++ b/packages/browser/src/__tests__/personProcessing.test.ts @@ -606,8 +606,9 @@ describe('person processing', () => { expect(eventAfterGroup[0].properties.$process_person_profile).toEqual(true) }) - it('should send the $groupidentify event even if person_processing is set to never', async () => { - // Groups are separate from person processing - $groupidentify should always be sent + it('should be ignored and log an error if person_processing is set to never', async () => { + // $groupidentify needs person processing, so the plugin server drops it when + // person processing is off. Gate group() like the other identity methods. // arrange const { posthog, beforeSendMock } = await setup('never') @@ -617,20 +618,17 @@ describe('person processing', () => { posthog.capture('custom event after group') // assert - // setGroupPropertiesForFlags still has a person processing check expect(mockLogger.error).toBeCalledTimes(1) expect(mockLogger.error).toHaveBeenCalledWith( - 'posthog.setGroupPropertiesForFlags was called, but process_person is set to "never". This call will be ignored.' + 'posthog.group was called, but process_person is set to "never". This call will be ignored.' ) - // $groupidentify is sent (groups are independent of person processing) - expect(beforeSendMock).toBeCalledTimes(3) + // no $groupidentify is sent, only the two custom events + expect(beforeSendMock).toBeCalledTimes(2) const eventBeforeGroup = beforeSendMock.mock.calls[0] expect(eventBeforeGroup[0].properties.$process_person_profile).toEqual(false) - const groupIdentify = beforeSendMock.mock.calls[1] - expect(groupIdentify[0].event).toEqual('$groupidentify') - // $groupidentify doesn't set $process_person_profile since it doesn't process persons - const eventAfterGroup = beforeSendMock.mock.calls[2] + const eventAfterGroup = beforeSendMock.mock.calls[1] + expect(eventAfterGroup[0].event).toEqual('custom event after group') expect(eventAfterGroup[0].properties.$process_person_profile).toEqual(false) }) }) diff --git a/packages/browser/src/posthog-core.ts b/packages/browser/src/posthog-core.ts index 92dae38264..5799f5d618 100644 --- a/packages/browser/src/posthog-core.ts +++ b/packages/browser/src/posthog-core.ts @@ -3195,6 +3195,10 @@ export class PostHog implements PostHogInterface { return } + if (!this._requirePersonProcessing('posthog.group')) { + return + } + // Apply a sibling reset before reading or writing groups so this explicit // mutation is newer than the adopted cookie snapshot. this.persistence?.syncCookieProperties() From e5fb5f43db8edbe4b8fff128f19eb838c3890bbd Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Wed, 2 Sep 2026 09:35:30 +0200 Subject: [PATCH 2/2] fix(browser): preserve group association without person profiles --- .changeset/gate-group-on-person-processing.md | 2 +- .../browser/src/__tests__/personProcessing.test.ts | 7 +++---- packages/browser/src/posthog-core.ts | 10 +++------- 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/.changeset/gate-group-on-person-processing.md b/.changeset/gate-group-on-person-processing.md index 2be96978f3..252c72ff21 100644 --- a/.changeset/gate-group-on-person-processing.md +++ b/.changeset/gate-group-on-person-processing.md @@ -2,4 +2,4 @@ 'posthog-js': patch --- -Gate `group()` on person processing, like the other identity methods. Under `identified_only` a `group()` call now promotes the user to identified, so the `$groupidentify` event is kept instead of dropped. Under `never` the call is ignored and logs an error, instead of sending an event the server always drops. +Gate `$groupidentify` on person processing. Under `identified_only`, a `group()` call promotes the user to identified so the event is kept instead of dropped. Under `never`, the local group association is retained for subsequent events and feature flags, but `$groupidentify` is not sent because the server always drops it. diff --git a/packages/browser/src/__tests__/personProcessing.test.ts b/packages/browser/src/__tests__/personProcessing.test.ts index 4ce5289f78..d07d82048a 100644 --- a/packages/browser/src/__tests__/personProcessing.test.ts +++ b/packages/browser/src/__tests__/personProcessing.test.ts @@ -606,9 +606,7 @@ describe('person processing', () => { expect(eventAfterGroup[0].properties.$process_person_profile).toEqual(true) }) - it('should be ignored and log an error if person_processing is set to never', async () => { - // $groupidentify needs person processing, so the plugin server drops it when - // person processing is off. Gate group() like the other identity methods. + it('should retain the group association without sending $groupidentify if person_processing is never', async () => { // arrange const { posthog, beforeSendMock } = await setup('never') @@ -620,7 +618,7 @@ describe('person processing', () => { // assert expect(mockLogger.error).toBeCalledTimes(1) expect(mockLogger.error).toHaveBeenCalledWith( - 'posthog.group was called, but process_person is set to "never". This call will be ignored.' + 'posthog.setGroupPropertiesForFlags was called, but process_person is set to "never". This call will be ignored.' ) // no $groupidentify is sent, only the two custom events @@ -630,6 +628,7 @@ describe('person processing', () => { const eventAfterGroup = beforeSendMock.mock.calls[1] expect(eventAfterGroup[0].event).toEqual('custom event after group') expect(eventAfterGroup[0].properties.$process_person_profile).toEqual(false) + expect(eventAfterGroup[0].properties.$groups).toEqual({ groupType: 'groupKey' }) }) }) diff --git a/packages/browser/src/posthog-core.ts b/packages/browser/src/posthog-core.ts index 5799f5d618..10d398ffc7 100644 --- a/packages/browser/src/posthog-core.ts +++ b/packages/browser/src/posthog-core.ts @@ -3195,10 +3195,6 @@ export class PostHog implements PostHogInterface { return } - if (!this._requirePersonProcessing('posthog.group')) { - return - } - // Apply a sibling reset before reading or writing groups so this explicit // mutation is newer than the adopted cookie snapshot. this.persistence?.syncCookieProperties() @@ -3214,9 +3210,9 @@ export class PostHog implements PostHogInterface { this.register({ $groups: { ...existingGroups, [groupType]: groupKey } }) // Send $groupidentify when the group is new/changed OR when properties - // are provided. Skip only when the group already exists with the same - // key and no new properties are being set. - if (isNewGroup || groupPropertiesToSet) { + // are provided, but only when the event can be processed. The local group + // association remains useful for events and feature flags without person processing. + if ((isNewGroup || groupPropertiesToSet) && this._hasPersonProcessing()) { const groupIdentifyProperties: Properties = { $group_type: groupType, $group_key: groupKey,