Skip to content

fix(node): fire separate $feature_flag_called events per group context#3658

Merged
gustavohstrassburger merged 6 commits into
mainfrom
gustavo/fix-flag-called-group-context
May 25, 2026
Merged

fix(node): fire separate $feature_flag_called events per group context#3658
gustavohstrassburger merged 6 commits into
mainfrom
gustavo/fix-flag-called-group-context

Conversation

@gustavohstrassburger
Copy link
Copy Markdown
Contributor

@gustavohstrassburger gustavohstrassburger commented May 22, 2026

Problem

In the Node SDK, $feature_flag_called events were not fired when the same flag+variant was evaluated under different group contexts. The deduplication key only included the flag key and variant, so only the first call would fire an event regardless of which group was used.

Closes #3651

Changes

Include group context in the $feature_flag_called deduplication key in _captureFlagCalledEventIfNeeded, so events fire independently per group combination.

How did you test this code?

Added a test in evaluate-flags.spec.ts verifying that two separate $feature_flag_called events fire for the same flag under different group contexts.

👉 Stay up-to-date with PostHog coding conventions for a smoother review.

Release info Sub-libraries affected

Libraries affected

  • All of them
  • posthog-js (web)
  • posthog-js-lite (web lite)
  • posthog-node
  • posthog-react-native
  • @posthog/react
  • @posthog/ai
  • @posthog/convex
  • @posthog/next
  • @posthog/nextjs-config
  • @posthog/nuxt
  • @posthog/rollup-plugin
  • @posthog/webpack-plugin
  • @posthog/types

Checklist

  • Tests for new code
  • Accounted for the impact of any changes across different platforms
  • Accounted for backwards compatibility of any changes (no breaking changes!)
  • Took care not to unnecessarily increase the bundle size

If releasing new changes

  • Ran pnpm changeset to generate a changeset file

@vercel
Copy link
Copy Markdown

vercel Bot commented May 22, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
posthog-example-next-app-router Ready Ready Preview May 25, 2026 5:58pm
posthog-js Ready Ready Preview May 25, 2026 5:58pm
posthog-nextjs-config Ready Ready Preview May 25, 2026 5:58pm

Request Review

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 22, 2026

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 22, 2026

Size Change: +368 B (0%)

Total Size: 16.4 MB

Filename Size Change
packages/node/dist/client.js 45.5 kB +184 B (+0.41%)
packages/node/dist/client.mjs 43.2 kB +184 B (+0.43%)
ℹ️ View Unchanged
Filename Size Change
packages/ai/dist/anthropic/index.cjs 26 kB 0 B
packages/ai/dist/anthropic/index.mjs 25.6 kB 0 B
packages/ai/dist/gemini/index.cjs 34.4 kB 0 B
packages/ai/dist/gemini/index.mjs 34.2 kB 0 B
packages/ai/dist/index.cjs 171 kB 0 B
packages/ai/dist/index.mjs 170 kB 0 B
packages/ai/dist/langchain/index.cjs 47.9 kB 0 B
packages/ai/dist/langchain/index.mjs 47.3 kB 0 B
packages/ai/dist/openai-agents/index.cjs 25.5 kB 0 B
packages/ai/dist/openai-agents/index.mjs 25.4 kB 0 B
packages/ai/dist/openai/index.cjs 50.7 kB 0 B
packages/ai/dist/openai/index.mjs 50.3 kB 0 B
packages/ai/dist/otel/index.cjs 4.97 kB 0 B
packages/ai/dist/otel/index.mjs 4.86 kB 0 B
packages/ai/dist/vercel/index.cjs 44.5 kB 0 B
packages/ai/dist/vercel/index.mjs 44.5 kB 0 B
packages/browser/dist/all-external-dependencies.js 260 kB 0 B
packages/browser/dist/array.full.es5.js 348 kB 0 B
packages/browser/dist/array.full.js 428 kB 0 B
packages/browser/dist/array.full.no-********.js 501 kB 0 B
packages/browser/dist/array.js 192 kB 0 B
packages/browser/dist/array.no-********.js 209 kB 0 B
packages/browser/dist/conversations.js 67.3 kB 0 B
packages/browser/dist/crisp-chat-integration.js 1.97 kB 0 B
packages/browser/dist/customizations.full.js 18 kB 0 B
packages/browser/dist/dead-clicks-autocapture.js 14.3 kB 0 B
packages/browser/dist/default-extensions.js 190 kB 0 B
packages/browser/dist/element-inference.js 5.69 kB 0 B
packages/browser/dist/exception-autocapture.js 11.8 kB 0 B
packages/browser/dist/extension-bundles.js 106 kB 0 B
packages/browser/dist/external-scripts-loader.js 3.13 kB 0 B
packages/browser/dist/intercom-integration.js 2.03 kB 0 B
packages/browser/dist/lazy-********.js 151 kB 0 B
packages/browser/dist/logs.js 38.9 kB 0 B
packages/browser/dist/main.js 196 kB 0 B
packages/browser/dist/module.full.js 431 kB 0 B
packages/browser/dist/module.full.no-********.js 504 kB 0 B
packages/browser/dist/module.js 196 kB 0 B
packages/browser/dist/module.no-********.js 213 kB 0 B
packages/browser/dist/module.slim.js 102 kB 0 B
packages/browser/dist/module.slim.no-********.js 107 kB 0 B
packages/browser/dist/posthog-********.js 151 kB 0 B
packages/browser/dist/product-tours-preview.js 76.4 kB 0 B
packages/browser/dist/product-tours.js 115 kB 0 B
packages/browser/dist/recorder-v2.js 98.7 kB 0 B
packages/browser/dist/recorder.js 98.7 kB 0 B
packages/browser/dist/rrweb-plugin-console-record.js 6.67 kB 0 B
packages/browser/dist/rrweb-types.js 2.28 kB 0 B
packages/browser/dist/rrweb.js 279 kB 0 B
packages/browser/dist/surveys-preview.js 76.3 kB 0 B
packages/browser/dist/surveys.js 94.7 kB 0 B
packages/browser/dist/tracing-headers.js 1.84 kB 0 B
packages/browser/dist/web-vitals-with-attribution.js 11.8 kB 0 B
packages/browser/dist/web-vitals.js 6.39 kB 0 B
packages/browser/react/dist/esm/index.js 21.2 kB 0 B
packages/browser/react/dist/esm/slim/index.js 17.6 kB 0 B
packages/browser/react/dist/esm/surveys/index.js 4.68 kB 0 B
packages/browser/react/dist/umd/index.js 24.4 kB 0 B
packages/browser/react/dist/umd/slim/index.js 20.4 kB 0 B
packages/browser/react/dist/umd/surveys/index.js 5.45 kB 0 B
packages/convex/dist/client/feature-flags/crypto.js 461 B 0 B
packages/convex/dist/client/feature-flags/evaluator.js 16.5 kB 0 B
packages/convex/dist/client/feature-flags/index.js 196 B 0 B
packages/convex/dist/client/feature-flags/match-********.js 14.8 kB 0 B
packages/convex/dist/client/feature-flags/types.js 44 B 0 B
packages/convex/dist/client/index.js 14.7 kB 0 B
packages/convex/dist/component/_generated/api.js 712 B 0 B
packages/convex/dist/component/_generated/component.js 212 B 0 B
packages/convex/dist/component/_generated/dataModel.js 230 B 0 B
packages/convex/dist/component/_generated/server.js 3.71 kB 0 B
packages/convex/dist/component/convex.config.js 133 B 0 B
packages/convex/dist/component/lib.js 19.9 kB 0 B
packages/convex/dist/component/schema.js 694 B 0 B
packages/convex/dist/component/version.js 67 B 0 B
packages/core/dist/cookie.js 5.34 kB 0 B
packages/core/dist/cookie.mjs 3.12 kB 0 B
packages/core/dist/error-tracking/chunk-ids.js 2.54 kB 0 B
packages/core/dist/error-tracking/chunk-ids.mjs 1.31 kB 0 B
packages/core/dist/error-tracking/coercers/dom-exception-coercer.js 2.3 kB 0 B
packages/core/dist/error-tracking/coercers/dom-exception-coercer.mjs 993 B 0 B
packages/core/dist/error-tracking/coercers/error-coercer.js 2.02 kB 0 B
packages/core/dist/error-tracking/coercers/error-coercer.mjs 794 B 0 B
packages/core/dist/error-tracking/coercers/error-event-coercer.js 1.76 kB 0 B
packages/core/dist/error-tracking/coercers/error-event-coercer.mjs 513 B 0 B
packages/core/dist/error-tracking/coercers/event-coercer.js 1.82 kB 0 B
packages/core/dist/error-tracking/coercers/event-coercer.mjs 548 B 0 B
packages/core/dist/error-tracking/coercers/index.js 6.79 kB 0 B
packages/core/dist/error-tracking/coercers/index.mjs 326 B 0 B
packages/core/dist/error-tracking/coercers/object-coercer.js 3.46 kB 0 B
packages/core/dist/error-tracking/coercers/object-coercer.mjs 2.07 kB 0 B
packages/core/dist/error-tracking/coercers/primitive-coercer.js 1.67 kB 0 B
packages/core/dist/error-tracking/coercers/primitive-coercer.mjs 419 B 0 B
packages/core/dist/error-tracking/coercers/promise-rejection-event.js 2.59 kB 0 B
packages/core/dist/error-tracking/coercers/promise-rejection-event.mjs 1.25 kB 0 B
packages/core/dist/error-tracking/coercers/string-coercer.js 2.01 kB 0 B
packages/core/dist/error-tracking/coercers/string-coercer.mjs 820 B 0 B
packages/core/dist/error-tracking/coercers/utils.js 2.06 kB 0 B
packages/core/dist/error-tracking/coercers/utils.mjs 716 B 0 B
packages/core/dist/error-tracking/error-properties-builder.js 5.56 kB 0 B
packages/core/dist/error-tracking/error-properties-builder.mjs 4.23 kB 0 B
packages/core/dist/error-tracking/exception-steps.js 6.87 kB 0 B
packages/core/dist/error-tracking/exception-steps.mjs 4.71 kB 0 B
packages/core/dist/error-tracking/index.js 4.74 kB 0 B
packages/core/dist/error-tracking/index.mjs 191 B 0 B
packages/core/dist/error-tracking/parsers/base.js 1.83 kB 0 B
packages/core/dist/error-tracking/parsers/base.mjs 464 B 0 B
packages/core/dist/error-tracking/parsers/chrome.js 2.73 kB 0 B
packages/core/dist/error-tracking/parsers/chrome.mjs 1.32 kB 0 B
packages/core/dist/error-tracking/parsers/gecko.js 2.47 kB 0 B
packages/core/dist/error-tracking/parsers/gecko.mjs 1.13 kB 0 B
packages/core/dist/error-tracking/parsers/index.js 4.75 kB 0 B
packages/core/dist/error-tracking/parsers/index.mjs 2.1 kB 0 B
packages/core/dist/error-tracking/parsers/node.js 3.94 kB 0 B
packages/core/dist/error-tracking/parsers/node.mjs 2.68 kB 0 B
packages/core/dist/error-tracking/parsers/opera.js 2.26 kB 0 B
packages/core/dist/error-tracking/parsers/opera.mjs 746 B 0 B
packages/core/dist/error-tracking/parsers/safari.js 1.88 kB 0 B
packages/core/dist/error-tracking/parsers/safari.mjs 574 B 0 B
packages/core/dist/error-tracking/parsers/winjs.js 1.72 kB 0 B
packages/core/dist/error-tracking/parsers/winjs.mjs 426 B 0 B
packages/core/dist/error-tracking/types.js 1.33 kB 0 B
packages/core/dist/error-tracking/types.mjs 131 B 0 B
packages/core/dist/error-tracking/utils.js 1.8 kB 0 B
packages/core/dist/error-tracking/utils.mjs 604 B 0 B
packages/core/dist/eventemitter.js 1.78 kB 0 B
packages/core/dist/eventemitter.mjs 571 B 0 B
packages/core/dist/featureFlagUtils.js 6.8 kB 0 B
packages/core/dist/featureFlagUtils.mjs 4.32 kB 0 B
packages/core/dist/gzip.js 5.72 kB 0 B
packages/core/dist/gzip.mjs 3.84 kB 0 B
packages/core/dist/index.js 13.6 kB 0 B
packages/core/dist/index.mjs 1.31 kB 0 B
packages/core/dist/logs/index.js 9.47 kB 0 B
packages/core/dist/logs/index.mjs 7.87 kB 0 B
packages/core/dist/logs/logs-utils.js 5.96 kB 0 B
packages/core/dist/logs/logs-utils.mjs 3.99 kB 0 B
packages/core/dist/logs/types.js 603 B 0 B
packages/core/dist/logs/types.mjs 0 B 0 B 🆕
packages/core/dist/posthog-core-stateless.js 33.5 kB 0 B
packages/core/dist/posthog-core-stateless.mjs 30.9 kB 0 B
packages/core/dist/posthog-core.js 42 kB 0 B
packages/core/dist/posthog-core.mjs 37 kB 0 B
packages/core/dist/surveys/events.js 4.21 kB 0 B
packages/core/dist/surveys/events.mjs 1.99 kB 0 B
packages/core/dist/surveys/index.js 4.57 kB 0 B
packages/core/dist/surveys/index.mjs 894 B 0 B
packages/core/dist/surveys/translations.js 9.4 kB 0 B
packages/core/dist/surveys/translations.mjs 7.03 kB 0 B
packages/core/dist/surveys/validation.js 3.06 kB 0 B
packages/core/dist/surveys/validation.mjs 1.51 kB 0 B
packages/core/dist/testing/index.js 2.93 kB 0 B
packages/core/dist/testing/index.mjs 79 B 0 B
packages/core/dist/testing/PostHogCoreTestClient.js 3.15 kB 0 B
packages/core/dist/testing/PostHogCoreTestClient.mjs 1.74 kB 0 B
packages/core/dist/testing/test-utils.js 2.83 kB 0 B
packages/core/dist/testing/test-utils.mjs 1.15 kB 0 B
packages/core/dist/tracing-headers.js 3.38 kB 0 B
packages/core/dist/tracing-headers.mjs 2.08 kB 0 B
packages/core/dist/types.js 9.62 kB 0 B
packages/core/dist/types.mjs 7.07 kB 0 B
packages/core/dist/utils/bot-detection.js 3.28 kB 0 B
packages/core/dist/utils/bot-detection.mjs 1.95 kB 0 B
packages/core/dist/utils/bucketed-rate-limiter.js 3 kB 0 B
packages/core/dist/utils/bucketed-rate-limiter.mjs 1.62 kB 0 B
packages/core/dist/utils/index.js 11.9 kB 0 B
packages/core/dist/utils/index.mjs 1.98 kB 0 B
packages/core/dist/utils/logger.js 2.58 kB 0 B
packages/core/dist/utils/logger.mjs 1.29 kB 0 B
packages/core/dist/utils/number-utils.js 3.32 kB 0 B
packages/core/dist/utils/number-utils.mjs 1.68 kB 0 B
packages/core/dist/utils/promise-queue.js 2 kB 0 B
packages/core/dist/utils/promise-queue.mjs 768 B 0 B
packages/core/dist/utils/string-utils.js 2.73 kB 0 B
packages/core/dist/utils/string-utils.mjs 1.09 kB 0 B
packages/core/dist/utils/type-utils.js 7.04 kB 0 B
packages/core/dist/utils/type-utils.mjs 3.11 kB 0 B
packages/core/dist/utils/user-agent-utils.js 15.5 kB 0 B
packages/core/dist/utils/user-agent-utils.mjs 12.4 kB 0 B
packages/core/dist/vendor/uuidv7.js 8.29 kB 0 B
packages/core/dist/vendor/uuidv7.mjs 6.72 kB 0 B
packages/next/dist/app/PostHogProvider.js 3.33 kB 0 B
packages/next/dist/client/ClientPostHogProvider.js 1.76 kB 0 B
packages/next/dist/client/hooks.js 172 B 0 B
packages/next/dist/client/PostHogPageView.js 1.76 kB 0 B
packages/next/dist/index.client.js 401 B 0 B
packages/next/dist/index.edge.js 447 B 0 B
packages/next/dist/index.js 444 B 0 B
packages/next/dist/index.react-server.js 420 B 0 B
packages/next/dist/middleware/postHogMiddleware.js 3.7 kB 0 B
packages/next/dist/pages.client.js 502 B 0 B
packages/next/dist/pages.edge.js 570 B 0 B
packages/next/dist/pages.js 414 B 0 B
packages/next/dist/pages/getServerSidePostHog.js 1.99 kB 0 B
packages/next/dist/pages/PostHogPageView.js 1.32 kB 0 B
packages/next/dist/pages/PostHogProvider.js 1.61 kB 0 B
packages/next/dist/server/getPostHog.js 2.79 kB 0 B
packages/next/dist/server/nodeClientCache.js 1.31 kB 0 B
packages/next/dist/shared/browser.js 195 B 0 B
packages/next/dist/shared/config.js 2.08 kB 0 B
packages/next/dist/shared/constants.js 201 B 0 B
packages/next/dist/shared/cookie.js 540 B 0 B
packages/next/dist/shared/identity.js 264 B 0 B
packages/next/dist/shared/tracing-headers.js 2.18 kB 0 B
packages/nextjs-config/dist/config.js 5.82 kB 0 B
packages/nextjs-config/dist/config.mjs 4.34 kB 0 B
packages/nextjs-config/dist/index.js 2.24 kB 0 B
packages/nextjs-config/dist/index.mjs 30 B 0 B
packages/nextjs-config/dist/utils.js 2.94 kB 0 B
packages/nextjs-config/dist/utils.mjs 826 B 0 B
packages/node/dist/entrypoints/index.edge.js 4.25 kB 0 B
packages/node/dist/entrypoints/index.edge.mjs 723 B 0 B
packages/node/dist/entrypoints/index.node.js 6.04 kB 0 B
packages/node/dist/entrypoints/index.node.mjs 1.22 kB 0 B
packages/node/dist/entrypoints/nestjs.js 2.31 kB 0 B
packages/node/dist/entrypoints/nestjs.mjs 42 B 0 B
packages/node/dist/experimental.js 870 B 0 B
packages/node/dist/experimental.mjs 267 B 0 B
packages/node/dist/exports.js 6.75 kB 0 B
packages/node/dist/exports.mjs 582 B 0 B
packages/node/dist/extensions/context/context.js 2.13 kB 0 B
packages/node/dist/extensions/context/context.mjs 863 B 0 B
packages/node/dist/extensions/context/types.js 603 B 0 B
packages/node/dist/extensions/context/types.mjs 0 B 0 B 🆕
packages/node/dist/extensions/error-tracking/autocapture.js 2.66 kB 0 B
packages/node/dist/extensions/error-tracking/autocapture.mjs 1.24 kB 0 B
packages/node/dist/extensions/error-tracking/index.js 4.14 kB 0 B
packages/node/dist/extensions/error-tracking/index.mjs 2.87 kB 0 B
packages/node/dist/extensions/error-tracking/modifiers/context-lines.node.js 8.81 kB 0 B
packages/node/dist/extensions/error-tracking/modifiers/context-lines.node.mjs 7.15 kB 0 B
packages/node/dist/extensions/error-tracking/modifiers/module.node.js 2.78 kB 0 B
packages/node/dist/extensions/error-tracking/modifiers/module.node.mjs 1.45 kB 0 B
packages/node/dist/extensions/error-tracking/modifiers/relative-path.node.js 1.97 kB 0 B
packages/node/dist/extensions/error-tracking/modifiers/relative-path.node.mjs 624 B 0 B
packages/node/dist/extensions/express.js 4.56 kB 0 B
packages/node/dist/extensions/express.mjs 2.45 kB 0 B
packages/node/dist/extensions/feature-flags/cache.js 603 B 0 B
packages/node/dist/extensions/feature-flags/cache.mjs 0 B 0 B 🆕
packages/node/dist/extensions/feature-flags/crypto.js 1.57 kB 0 B
packages/node/dist/extensions/feature-flags/crypto.mjs 395 B 0 B
packages/node/dist/extensions/feature-flags/feature-flags.js 40.6 kB 0 B
packages/node/dist/extensions/feature-flags/feature-flags.mjs 38.5 kB 0 B
packages/node/dist/extensions/nestjs.js 5 kB 0 B
packages/node/dist/extensions/nestjs.mjs 2.9 kB 0 B
packages/node/dist/extensions/sentry-integration.js 4.66 kB 0 B
packages/node/dist/extensions/sentry-integration.mjs 3.17 kB 0 B
packages/node/dist/extensions/tracing-headers.js 3.31 kB 0 B
packages/node/dist/extensions/tracing-headers.mjs 1.53 kB 0 B
packages/node/dist/feature-flag-evaluations.js 5.97 kB 0 B
packages/node/dist/feature-flag-evaluations.mjs 4.63 kB 0 B
packages/node/dist/storage-memory.js 1.52 kB 0 B
packages/node/dist/storage-memory.mjs 297 B 0 B
packages/node/dist/types.js 1.43 kB 0 B
packages/node/dist/types.mjs 224 B 0 B
packages/node/dist/version.js 1.21 kB 0 B
packages/node/dist/version.mjs 46 B 0 B
packages/nuxt/dist/module.mjs 5.29 kB 0 B
packages/nuxt/dist/runtime/composables/useFeatureFlagEnabled.js 566 B 0 B
packages/nuxt/dist/runtime/composables/useFeatureFlagPayload.js 690 B 0 B
packages/nuxt/dist/runtime/composables/useFeatureFlagVariantKey.js 591 B 0 B
packages/nuxt/dist/runtime/composables/usePostHog.js 128 B 0 B
packages/nuxt/dist/runtime/nitro-plugin.js 1.08 kB 0 B
packages/nuxt/dist/runtime/vue-plugin.js 1.14 kB 0 B
packages/plugin-utils/dist/cli.js 3.14 kB 0 B
packages/plugin-utils/dist/cli.mjs 1.64 kB 0 B
packages/plugin-utils/dist/config.js 3.07 kB 0 B
packages/plugin-utils/dist/config.mjs 1.83 kB 0 B
packages/plugin-utils/dist/index.js 4.3 kB 0 B
packages/plugin-utils/dist/index.mjs 217 B 0 B
packages/plugin-utils/dist/spawn-local.js 2.17 kB 0 B
packages/plugin-utils/dist/spawn-local.mjs 918 B 0 B
packages/plugin-utils/dist/utils.js 3.27 kB 0 B
packages/plugin-utils/dist/utils.mjs 1.3 kB 0 B
packages/react-native/dist/autocapture.js 5.05 kB 0 B
packages/react-native/dist/error-tracking/index.js 7.24 kB 0 B
packages/react-native/dist/error-tracking/utils.js 2.58 kB 0 B
packages/react-native/dist/frameworks/wix-navigation.js 1.3 kB 0 B
packages/react-native/dist/hooks/useFeatureFlag.js 1.7 kB 0 B
packages/react-native/dist/hooks/useFeatureFlagResult.js 963 B 0 B
packages/react-native/dist/hooks/useFeatureFlags.js 921 B 0 B
packages/react-native/dist/hooks/useNavigationTracker.js 2.45 kB 0 B
packages/react-native/dist/hooks/usePostHog.js 544 B 0 B
packages/react-native/dist/hooks/utils.js 988 B 0 B
packages/react-native/dist/index.js 4.33 kB 0 B
packages/react-native/dist/logs-********.js 3.32 kB 0 B
packages/react-native/dist/native-deps.js 8.77 kB 0 B
packages/react-native/dist/optional/OptionalAsyncStorage.js 299 B 0 B
packages/react-native/dist/optional/OptionalExpoApplication.js 377 B 0 B
packages/react-native/dist/optional/OptionalExpoDevice.js 347 B 0 B
packages/react-native/dist/optional/OptionalExpoFileSystem.js 386 B 0 B
packages/react-native/dist/optional/OptionalExpoFileSystemLegacy.js 423 B 0 B
packages/react-native/dist/optional/OptionalExpoLocalization.js 383 B 0 B
packages/react-native/dist/optional/OptionalReactNativeDeviceInfo.js 415 B 0 B
packages/react-native/dist/optional/OptionalReactNativeLocalize.js 303 B 0 B
packages/react-native/dist/optional/OptionalReactNativeNavigation.js 415 B 0 B
packages/react-native/dist/optional/OptionalReactNativeNavigationWix.js 443 B 0 B
packages/react-native/dist/optional/OptionalReactNativeSafeArea.js 644 B 0 B
packages/react-native/dist/optional/OptionalReactNativeSvg.js 872 B 0 B
packages/react-native/dist/optional/OptionalSessionReplay.js 455 B 0 B
packages/react-native/dist/posthog-rn.js 46 kB 0 B
packages/react-native/dist/PostHogContext.js 329 B 0 B
packages/react-native/dist/PostHogErrorBoundary.js 3.19 kB 0 B
packages/react-native/dist/PostHogMaskView.js 1.68 kB 0 B
packages/react-native/dist/PostHogProvider.js 4.55 kB 0 B
packages/react-native/dist/storage.js 5.2 kB 0 B
packages/react-native/dist/surveys/components/BottomSection.js 1.46 kB 0 B
packages/react-native/dist/surveys/components/Cancel.js 909 B 0 B
packages/react-native/dist/surveys/components/ConfirmationMessage.js 1.65 kB 0 B
packages/react-native/dist/surveys/components/QuestionHeader.js 1.37 kB 0 B
packages/react-native/dist/surveys/components/QuestionTypes.js 13.3 kB 0 B
packages/react-native/dist/surveys/components/SurveyModal.js 6.27 kB 0 B
packages/react-native/dist/surveys/components/Surveys.js 6.58 kB 0 B
packages/react-native/dist/surveys/getActiveMatchingSurveys.js 2.64 kB 0 B
packages/react-native/dist/surveys/icons.js 9.97 kB 0 B
packages/react-native/dist/surveys/index.js 600 B 0 B
packages/react-native/dist/surveys/PostHogSurveyProvider.js 6.28 kB 0 B
packages/react-native/dist/surveys/survey-translations.js 1.11 kB 0 B
packages/react-native/dist/surveys/surveys-utils.js 14.2 kB 0 B
packages/react-native/dist/surveys/useActivatedSurveys.js 3.67 kB 0 B
packages/react-native/dist/surveys/useSurveyStorage.js 2.16 kB 0 B
packages/react-native/dist/tooling/expoconfig.js 4.02 kB 0 B
packages/react-native/dist/tooling/metroconfig.js 2.32 kB 0 B
packages/react-native/dist/tooling/posthogMetroSerializer.js 4.86 kB 0 B
packages/react-native/dist/tooling/utils.js 4.05 kB 0 B
packages/react-native/dist/tooling/vendor/expo/expoconfig.js 70 B 0 B
packages/react-native/dist/tooling/vendor/metro/countLines.js 237 B 0 B
packages/react-native/dist/tooling/vendor/metro/utils.js 3.35 kB 0 B
packages/react-native/dist/types.js 70 B 0 B
packages/react-native/dist/utils.js 1.14 kB 0 B
packages/react-native/dist/version.js 131 B 0 B
packages/react/dist/esm/index.js 21.2 kB 0 B
packages/react/dist/esm/slim/index.js 17.6 kB 0 B
packages/react/dist/esm/surveys/index.js 4.68 kB 0 B
packages/react/dist/umd/index.js 24.4 kB 0 B
packages/react/dist/umd/slim/index.js 20.4 kB 0 B
packages/react/dist/umd/surveys/index.js 5.45 kB 0 B
packages/rollup-plugin/dist/index.js 2.44 kB 0 B
packages/rrweb/all/dist/rrweb-all.cjs 611 kB 0 B
packages/rrweb/all/dist/rrweb-all.js 611 kB 0 B
packages/rrweb/all/dist/rrweb-all.umd.cjs 614 kB 0 B
packages/rrweb/all/dist/rrweb-all.umd.min.cjs 290 kB 0 B
packages/rrweb/packer/dist/base-********.js 18.2 kB 0 B
packages/rrweb/packer/dist/base-********.cjs 18.3 kB 0 B
packages/rrweb/packer/dist/base-********.umd.cjs 18.7 kB 0 B
packages/rrweb/packer/dist/base-********.umd.min.cjs 9.5 kB 0 B
packages/rrweb/packer/dist/pack.cjs 347 B 0 B
packages/rrweb/packer/dist/pack.js 285 B 0 B
packages/rrweb/packer/dist/pack.umd.cjs 1.63 kB 0 B
packages/rrweb/packer/dist/pack.umd.min.cjs 1.11 kB 0 B
packages/rrweb/packer/dist/packer.cjs 257 B 0 B
packages/rrweb/packer/dist/packer.js 136 B 0 B
packages/rrweb/packer/dist/packer.umd.cjs 662 B 0 B
packages/rrweb/packer/dist/packer.umd.min.cjs 626 B 0 B
packages/rrweb/packer/dist/unpack.cjs 769 B 0 B
packages/rrweb/packer/dist/unpack.js 702 B 0 B
packages/rrweb/packer/dist/unpack.umd.cjs 1.17 kB 0 B
packages/rrweb/packer/dist/unpack.umd.min.cjs 955 B 0 B
packages/rrweb/plugins/rrweb-plugin-canvas-webrtc-record/dist/rrweb-plugin-canvas-webrtc-record.cjs 37.6 kB 0 B
packages/rrweb/plugins/rrweb-plugin-canvas-webrtc-record/dist/rrweb-plugin-canvas-webrtc-record.js 37.5 kB 0 B
packages/rrweb/plugins/rrweb-plugin-canvas-webrtc-record/dist/rrweb-plugin-canvas-webrtc-record.umd.cjs 38 kB 0 B
packages/rrweb/plugins/rrweb-plugin-canvas-webrtc-record/dist/rrweb-plugin-canvas-webrtc-record.umd.min.cjs 22.2 kB 0 B
packages/rrweb/plugins/rrweb-plugin-canvas-webrtc-replay/dist/rrweb-plugin-canvas-webrtc-replay.cjs 34.3 kB 0 B
packages/rrweb/plugins/rrweb-plugin-canvas-webrtc-replay/dist/rrweb-plugin-canvas-webrtc-replay.js 34.2 kB 0 B
packages/rrweb/plugins/rrweb-plugin-canvas-webrtc-replay/dist/rrweb-plugin-canvas-webrtc-replay.umd.cjs 34.7 kB 0 B
packages/rrweb/plugins/rrweb-plugin-canvas-webrtc-replay/dist/rrweb-plugin-canvas-webrtc-replay.umd.min.cjs 20.5 kB 0 B
packages/rrweb/plugins/rrweb-plugin-console-record/dist/rrweb-plugin-console-record.cjs 14.9 kB 0 B
packages/rrweb/plugins/rrweb-plugin-console-record/dist/rrweb-plugin-console-record.js 14.8 kB 0 B
packages/rrweb/plugins/rrweb-plugin-console-record/dist/rrweb-plugin-console-record.umd.cjs 15.4 kB 0 B
packages/rrweb/plugins/rrweb-plugin-console-record/dist/rrweb-plugin-console-record.umd.min.cjs 7.33 kB 0 B
packages/rrweb/plugins/rrweb-plugin-console-replay/dist/rrweb-plugin-console-replay.cjs 5.01 kB 0 B
packages/rrweb/plugins/rrweb-plugin-console-replay/dist/rrweb-plugin-console-replay.js 4.9 kB 0 B
packages/rrweb/plugins/rrweb-plugin-console-replay/dist/rrweb-plugin-console-replay.umd.cjs 5.44 kB 0 B
packages/rrweb/plugins/rrweb-plugin-console-replay/dist/rrweb-plugin-console-replay.umd.min.cjs 2.64 kB 0 B
packages/rrweb/plugins/rrweb-plugin-sequential-id-record/dist/rrweb-plugin-sequential-id-record.cjs 681 B 0 B
packages/rrweb/plugins/rrweb-plugin-sequential-id-record/dist/rrweb-plugin-sequential-id-record.js 548 B 0 B
packages/rrweb/plugins/rrweb-plugin-sequential-id-record/dist/rrweb-plugin-sequential-id-record.umd.cjs 1.12 kB 0 B
packages/rrweb/plugins/rrweb-plugin-sequential-id-record/dist/rrweb-plugin-sequential-id-record.umd.min.cjs 829 B 0 B
packages/rrweb/plugins/rrweb-plugin-sequential-id-replay/dist/rrweb-plugin-sequential-id-replay.cjs 933 B 0 B
packages/rrweb/plugins/rrweb-plugin-sequential-id-replay/dist/rrweb-plugin-sequential-id-replay.js 820 B 0 B
packages/rrweb/plugins/rrweb-plugin-sequential-id-replay/dist/rrweb-plugin-sequential-id-replay.umd.cjs 1.37 kB 0 B
packages/rrweb/plugins/rrweb-plugin-sequential-id-replay/dist/rrweb-plugin-sequential-id-replay.umd.min.cjs 968 B 0 B
packages/rrweb/record/dist/rrweb-record.cjs 173 kB 0 B
packages/rrweb/record/dist/rrweb-record.js 173 kB 0 B
packages/rrweb/record/dist/rrweb-record.umd.cjs 174 kB 0 B
packages/rrweb/record/dist/rrweb-record.umd.min.cjs 83.2 kB 0 B
packages/rrweb/replay/dist/rrweb-replay.cjs 440 kB 0 B
packages/rrweb/replay/dist/rrweb-replay.js 439 kB 0 B
packages/rrweb/replay/dist/rrweb-replay.umd.cjs 442 kB 0 B
packages/rrweb/replay/dist/rrweb-replay.umd.min.cjs 209 kB 0 B
packages/rrweb/rrdom-nodejs/dist/rrdom-nodejs.cjs 150 kB 0 B
packages/rrweb/rrdom-nodejs/dist/rrdom-nodejs.js 149 kB 0 B
packages/rrweb/rrdom-nodejs/dist/rrdom-nodejs.umd.cjs 152 kB 0 B
packages/rrweb/rrdom-nodejs/dist/rrdom-nodejs.umd.min.cjs 70.8 kB 0 B
packages/rrweb/rrdom/dist/rrdom.cjs 174 kB 0 B
packages/rrweb/rrdom/dist/rrdom.js 173 kB 0 B
packages/rrweb/rrdom/dist/rrdom.umd.cjs 175 kB 0 B
packages/rrweb/rrdom/dist/rrdom.umd.min.cjs 80.8 kB 0 B
packages/rrweb/rrweb-snapshot/dist/record.cjs 31.8 kB 0 B
packages/rrweb/rrweb-snapshot/dist/record.js 30.8 kB 0 B
packages/rrweb/rrweb-snapshot/dist/record.umd.cjs 53.1 kB 0 B
packages/rrweb/rrweb-snapshot/dist/record.umd.min.cjs 25.5 kB 0 B
packages/rrweb/rrweb-snapshot/dist/replay.cjs 137 kB 0 B
packages/rrweb/rrweb-snapshot/dist/replay.js 137 kB 0 B
packages/rrweb/rrweb-snapshot/dist/replay.umd.cjs 160 kB 0 B
packages/rrweb/rrweb-snapshot/dist/replay.umd.min.cjs 73.8 kB 0 B
packages/rrweb/rrweb-snapshot/dist/rrweb-********.cjs 2.19 kB 0 B
packages/rrweb/rrweb-snapshot/dist/rrweb-********.js 1.36 kB 0 B
packages/rrweb/rrweb-snapshot/dist/rrweb-********.umd.cjs 216 kB 0 B
packages/rrweb/rrweb-snapshot/dist/rrweb-********.umd.min.cjs 91.3 kB 0 B
packages/rrweb/rrweb-snapshot/dist/types-********.cjs 18.3 kB 0 B
packages/rrweb/rrweb-snapshot/dist/types-********.umd.cjs 18.8 kB 0 B
packages/rrweb/rrweb-snapshot/dist/types-********.umd.min.cjs 9.31 kB 0 B
packages/rrweb/rrweb-snapshot/dist/types-********.js 17.8 kB 0 B
packages/rrweb/rrweb/dist/rrweb.cjs 594 kB 0 B
packages/rrweb/rrweb/dist/rrweb.js 594 kB 0 B
packages/rrweb/rrweb/dist/rrweb.umd.cjs 594 kB 0 B
packages/rrweb/rrweb/dist/rrweb.umd.min.cjs 280 kB 0 B
packages/rrweb/types/dist/rrweb-types.cjs 5.64 kB 0 B
packages/rrweb/types/dist/rrweb-types.js 5.38 kB 0 B
packages/rrweb/types/dist/rrweb-types.umd.cjs 6.04 kB 0 B
packages/rrweb/types/dist/rrweb-types.umd.min.cjs 2.8 kB 0 B
packages/rrweb/utils/dist/rrweb-utils.cjs 6.41 kB 0 B
packages/rrweb/utils/dist/rrweb-utils.js 5.95 kB 0 B
packages/rrweb/utils/dist/rrweb-utils.umd.cjs 6.82 kB 0 B
packages/rrweb/utils/dist/rrweb-utils.umd.min.cjs 3.51 kB 0 B
packages/types/dist/capture-log.js 603 B 0 B
packages/types/dist/capture-log.mjs 0 B 0 B 🆕
packages/types/dist/capture.js 603 B 0 B
packages/types/dist/capture.mjs 0 B 0 B 🆕
packages/types/dist/common.js 603 B 0 B
packages/types/dist/common.mjs 0 B 0 B 🆕
packages/types/dist/feature-flags.js 603 B 0 B
packages/types/dist/feature-flags.mjs 0 B 0 B 🆕
packages/types/dist/index.js 603 B 0 B
packages/types/dist/index.mjs 0 B 0 B 🆕
packages/types/dist/posthog-config.js 603 B 0 B
packages/types/dist/posthog-config.mjs 0 B 0 B 🆕
packages/types/dist/posthog.js 603 B 0 B
packages/types/dist/posthog.mjs 0 B 0 B 🆕
packages/types/dist/request.js 603 B 0 B
packages/types/dist/request.mjs 0 B 0 B 🆕
packages/types/dist/segment.js 603 B 0 B
packages/types/dist/segment.mjs 0 B 0 B 🆕
packages/types/dist/session-recording.js 603 B 0 B
packages/types/dist/session-recording.mjs 0 B 0 B 🆕
packages/types/dist/survey.js 603 B 0 B
packages/types/dist/survey.mjs 0 B 0 B 🆕
packages/types/dist/toolbar.js 603 B 0 B
packages/types/dist/toolbar.mjs 0 B 0 B 🆕
packages/types/dist/tree-shakeable.js 603 B 0 B
packages/types/dist/tree-shakeable.mjs 0 B 0 B 🆕
packages/web/dist/index.cjs 13.8 kB 0 B
packages/web/dist/index.mjs 13.7 kB 0 B
packages/webpack-plugin/dist/config.js 1.53 kB 0 B
packages/webpack-plugin/dist/config.mjs 543 B 0 B
packages/webpack-plugin/dist/index.js 5.38 kB 0 B
packages/webpack-plugin/dist/index.mjs 2.04 kB 0 B
tooling/changelog/dist/index.js 3.31 kB 0 B
tooling/rollup-utils/dist/index.js 1.17 kB 0 B

compressed-size-action

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 22, 2026

Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
packages/node/src/client.ts:1762
The custom sort comparator can be replaced with `localeCompare`, which is the idiomatic TypeScript approach for lexicographic string sorting and communicates intent more clearly.

```suggestion
        ? `_${JSON.stringify(Object.fromEntries(Object.entries(groups).sort(([a], [b]) => a.localeCompare(b))))}`
```

### Issue 2 of 2
packages/node/src/__tests__/evaluate-flags.spec.ts:195-219
**Prefer parameterised tests for related deduplication cases**

The two deduplication tests — "repeated access under the same group context" and "same groups in different key order" — both assert `toHaveLength(1)` and differ only in how the two evaluations are set up. Per the project's simplicity rules, grouping them with `it.each` would express the shared assertion once and make adding future deduplication scenarios straightforward without repeating the filter/expect boilerplate.

Reviews (1): Last reviewed commit: "fix(node): harden flag-called dedup key ..." | Re-trigger Greptile

Comment thread packages/node/src/client.ts Outdated
Comment thread packages/node/src/__tests__/evaluate-flags.spec.ts
Copy link
Copy Markdown
Contributor

@dmarticus dmarticus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice fix — the sorted JSON serialization makes the dedup key stable across insertion order, and the new tests cover the important cases (different groups fire separately, same groups dedup, key-order normalized).

Left a couple of optional style nits inline plus a flag on a pre-existing cache concern this PR amplifies (not a blocker). Reminder to add a patch changeset for posthog-node before merging — the changeset-hygiene bot already pinged on it.

Comment thread packages/node/src/client.ts Outdated
groups && Object.keys(groups).length > 0
? `_${JSON.stringify(Object.fromEntries(Object.entries(groups).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))))}`
: ''
const featureFlagReportedKey = `${key}_${response}${groupSuffix}`
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a blocker for this PR, just flagging for follow-up: the existing eviction logic just below only fires on the number of distinct IDs — the inner string[] per distinctId has no cap, and each membership check is Array.includes (O(n)).

This PR amplifies the existing risk since dedup cardinality goes from flags × variants to flags × variants × group-combinations. For long-running Node processes that evaluate flags for one logical user across many tenants/groups, the inner array can grow and lookups slow down. Worth a follow-up to bound it (LRU) or switch to a Set for O(1) lookup.

(m) => m.event === '$feature_flag_called' && m.properties.$feature_flag === 'boolean-flag'
)
expect(flagCalled).toHaveLength(1)
})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional: consider locking down that groups: undefined and groups: {} dedup together. Both currently produce no suffix, so a single test asserting they collide would pin that contract — useful since users who omit groups on one call and pass {} on another would otherwise be surprised by either outcome changing.

@github-project-automation github-project-automation Bot moved this from In Review to Approved in Feature Flags May 22, 2026
@gustavohstrassburger gustavohstrassburger requested a review from a team as a code owner May 25, 2026 17:19
@gustavohstrassburger gustavohstrassburger merged commit 5d7a2d3 into main May 25, 2026
49 checks passed
@gustavohstrassburger gustavohstrassburger deleted the gustavo/fix-flag-called-group-context branch May 25, 2026 18:03
@github-project-automation github-project-automation Bot moved this from Approved to Done in Feature Flags May 25, 2026
gustavohstrassburger added a commit to PostHog/posthog-python that referenced this pull request May 25, 2026
In `_capture_feature_flag_called_if_needed`, the per-distinct_id dedupe key only included the flag key and response. For group-scoped flags, this meant that when the same user was evaluated under a different group, no new `$feature_flag_called` event was fired — causing per-group exposure undercount for experiments scoped to a group key.

Include the sorted `groups` map in the dedupe key so that the same (distinct_id, flag, response) combination fires once per distinct group context. Repeated calls under the same group context still dedupe, and calls that pass the same map in a different key order still dedupe (the groups are canonicalized via `sorted(groups.items())`).

Mirrors the posthog-node fix in PostHog/posthog-js#3658.

Generated-By: PostHog Code
Task-Id: d94308d9-7655-4bac-8f15-c61478b5fca1
gustavohstrassburger added a commit to PostHog/posthog-ruby that referenced this pull request May 25, 2026
In `_capture_feature_flag_called_if_needed`, the per-`distinct_id` dedupe key only included the flag key and response. For group-scoped flags this meant that when the same user was evaluated under a different group, no new `$feature_flag_called` event was fired — causing per-group exposure undercount for experiments scoped to a group key.

Include the sorted `groups` hash in the dedupe key so that the same `(distinct_id, flag, response)` combination fires once per distinct group context. Repeated calls under the same group context still dedupe, and calls that pass the same map in a different key order still dedupe (the groups are canonicalized via `groups.sort.to_json` before being mixed into the dedup key).

Mirrors the posthog-node fix in PostHog/posthog-js#3658 (which closes PostHog/posthog-js#3651). Both SDKs share the same dedupe shape, so backend evaluation needs the same change.

Generated-By: PostHog Code
Task-Id: d94308d9-7655-4bac-8f15-c61478b5fca1
gustavohstrassburger added a commit to PostHog/posthog-go that referenced this pull request May 25, 2026
In `captureFlagCalledIfNeeded`, the per-`distinct_id` LRU dedupe key only included `(distinct_id, flag_key, device_id)`. For group-scoped flags this meant that when the same user was evaluated under a different group, no new `$feature_flag_called` event was fired — causing per-group exposure undercount for experiments scoped to a group key.

Add the group context as a canonical JSON of the sorted group key/value pairs to the LRU cache key (new `groupsRepr` field on `flagUser`) so the same `(user, flag, device)` fires once per distinct group context. Repeated calls under the same group context still dedupe; calls with the same map but different Go map insertion order also dedupe (the JSON is built from a `sort.Strings`-sorted slice of keys).

Mirrors the posthog-node fix in PostHog/posthog-js#3658 (which closes PostHog/posthog-js#3651). Both SDKs share the same dedupe shape, so backend evaluation needs the same change.

Generated-By: PostHog Code
Task-Id: d94308d9-7655-4bac-8f15-c61478b5fca1
gustavohstrassburger added a commit to PostHog/posthog-php that referenced this pull request May 25, 2026
In `Client::captureFlagCalledIfNeeded`, the per-`distinct_id` dedupe element only included the distinct ID under the flag key. For group-scoped flags this meant that when the same user was evaluated under a different group, no new `$feature_flag_called` event was fired — causing per-group exposure undercount for experiments scoped to a group key.

Append a canonical JSON of the sorted `groups` map to the dedup element (`$distinctId . canonicalGroupsRepr($groups)`) so the same `(distinct_id, flag)` fires once per distinct group context. Repeated calls under the same group context still dedupe; calls that pass the same array with keys inserted in a different order also dedupe (the array is canonicalized via `ksort` before being JSON-encoded).

Mirrors the posthog-node fix in PostHog/posthog-js#3658 (which closes PostHog/posthog-js#3651). Both SDKs share the same dedupe shape, so backend evaluation needs the same change.

Generated-By: PostHog Code
Task-Id: d94308d9-7655-4bac-8f15-c61478b5fca1
gustavohstrassburger added a commit to PostHog/posthog-dotnet that referenced this pull request May 25, 2026
In `PostHogClient.TryCaptureDedupedFeatureFlagCalledEvent`, the per-`distinctId` `MemoryCache` key only included `(distinctId, featureKey, cacheKeyValue)`. For group-scoped flags this meant that when the same user was evaluated under a different group, no new `$feature_flag_called` event was fired — causing per-group exposure undercount for experiments scoped to a group key.

Include a canonical representation of the `GroupCollection` in the cache key (new `CanonicalGroupsCacheKey` helper) so the same `(user, flag, response)` fires once per distinct group context. Repeated calls under the same group context still dedupe; group collections built in a different insertion order also dedupe (canonicalized via `OrderBy(GroupType, StringComparer.Ordinal)`).

Mirrors the posthog-node fix in PostHog/posthog-js#3658 (which closes PostHog/posthog-js#3651). Both SDKs share the same dedupe shape, so backend evaluation needs the same change.

Generated-By: PostHog Code
Task-Id: d94308d9-7655-4bac-8f15-c61478b5fca1
gustavohstrassburger added a commit to PostHog/posthog-android that referenced this pull request May 25, 2026
In `PostHogFeatureFlagCalledCache.add`, the per-`distinctId` LRU dedupe key only included `(distinctId, flagKey, value)`. For group-scoped flags this meant that when the same user was evaluated under a different group, no new `$feature_flag_called` event was fired — causing per-group exposure undercount for experiments scoped to a group key.

Add a canonical groups representation (`canonicalGroupsRepr`) into the LRU cache key — built from the sorted group entries so two equal maps with keys inserted in a different order produce the same dedup key. The `groups` map is now threaded through `PostHogStateless.captureFeatureFlagCalledEvent` and the `posthog-server` snapshot accessors (`PostHogFeatureFlagEvaluations.isEnabled/getFlag`) so it can be funneled into the dedup cache and onto the captured `$feature_flag_called` event payload.

This affects two modules:
- `posthog/` (PostHogStateless / cache) — `captureFeatureFlagCalledEvent` now takes an optional `groups` parameter (added via `@JvmOverloads` so the existing Java-callable signature still exists).
- `posthog-server/` — the snapshot carries the groups it was evaluated under and passes them to the host's dedup-aware capture call.

Mirrors the posthog-node fix in PostHog/posthog-js#3658 (which closes PostHog/posthog-js#3651). Both SDKs share the same dedupe shape, so backend evaluation needs the same change.

Generated-By: PostHog Code
Task-Id: d94308d9-7655-4bac-8f15-c61478b5fca1
gustavohstrassburger added a commit to PostHog/posthog-rs that referenced this pull request May 25, 2026
In `build_dedup_key` (in both `client/async_client.rs` and `client/blocking.rs`), the per-`distinct_id` dedup key only included `(flag_key, response)`. For group-scoped flags this meant that when the same user was evaluated under a different group, no new `$feature_flag_called` event was fired — causing per-group exposure undercount for experiments scoped to a group key.

`build_dedup_key` now also takes the `groups` map; when non-empty it appends a canonical, sorted `"k1=v1;k2=v2;…"` suffix so the same `(user, flag, response)` fires once per distinct group context. Repeated calls under the same group context still dedupe; calls with the same group map in a different insertion order also dedupe (sorted via `sort_by(|a, b| a.0.cmp(b.0))`).

Mirrors the posthog-node fix in PostHog/posthog-js#3658 (which closes PostHog/posthog-js#3651). Both SDKs share the same dedupe shape, so backend evaluation needs the same change.

Generated-By: PostHog Code
Task-Id: d94308d9-7655-4bac-8f15-c61478b5fca1
gustavohstrassburger added a commit to PostHog/posthog-python that referenced this pull request May 27, 2026
* fix: include group context in $feature_flag_called dedupe key

In `_capture_feature_flag_called_if_needed`, the per-distinct_id dedupe key only included the flag key and response. For group-scoped flags, this meant that when the same user was evaluated under a different group, no new `$feature_flag_called` event was fired — causing per-group exposure undercount for experiments scoped to a group key.

Include the sorted `groups` map in the dedupe key so that the same (distinct_id, flag, response) combination fires once per distinct group context. Repeated calls under the same group context still dedupe, and calls that pass the same map in a different key order still dedupe (the groups are canonicalized via `sorted(groups.items())`).

Mirrors the posthog-node fix in PostHog/posthog-js#3658.

Generated-By: PostHog Code
Task-Id: d94308d9-7655-4bac-8f15-c61478b5fca1

* chore: add changeset



Generated-By: PostHog Code
Task-Id: d94308d9-7655-4bac-8f15-c61478b5fca1

* refactor: use tuple for feature_flag_reported_key and parameterize dedupe tests
gustavohstrassburger added a commit to PostHog/posthog-php that referenced this pull request May 27, 2026
* fix: include group context in $feature_flag_called dedupe key

In `Client::captureFlagCalledIfNeeded`, the per-`distinct_id` dedupe element only included the distinct ID under the flag key. For group-scoped flags this meant that when the same user was evaluated under a different group, no new `$feature_flag_called` event was fired — causing per-group exposure undercount for experiments scoped to a group key.

Append a canonical JSON of the sorted `groups` map to the dedup element (`$distinctId . canonicalGroupsRepr($groups)`) so the same `(distinct_id, flag)` fires once per distinct group context. Repeated calls under the same group context still dedupe; calls that pass the same array with keys inserted in a different order also dedupe (the array is canonicalized via `ksort` before being JSON-encoded).

Mirrors the posthog-node fix in PostHog/posthog-js#3658 (which closes PostHog/posthog-js#3651). Both SDKs share the same dedupe shape, so backend evaluation needs the same change.

Generated-By: PostHog Code
Task-Id: d94308d9-7655-4bac-8f15-c61478b5fca1

* chore: add changeset



Generated-By: PostHog Code
Task-Id: d94308d9-7655-4bac-8f15-c61478b5fca1

* fix: add JSON_THROW_ON_ERROR to group dedup key encoding and combine dedup tests under dataProvider
gustavohstrassburger added a commit to PostHog/posthog-ruby that referenced this pull request May 27, 2026
* fix: include group context in $feature_flag_called dedupe key

In `_capture_feature_flag_called_if_needed`, the per-`distinct_id` dedupe key only included the flag key and response. For group-scoped flags this meant that when the same user was evaluated under a different group, no new `$feature_flag_called` event was fired — causing per-group exposure undercount for experiments scoped to a group key.

Include the sorted `groups` hash in the dedupe key so that the same `(distinct_id, flag, response)` combination fires once per distinct group context. Repeated calls under the same group context still dedupe, and calls that pass the same map in a different key order still dedupe (the groups are canonicalized via `groups.sort.to_json` before being mixed into the dedup key).

Mirrors the posthog-node fix in PostHog/posthog-js#3658 (which closes PostHog/posthog-js#3651). Both SDKs share the same dedupe shape, so backend evaluation needs the same change.

Generated-By: PostHog Code
Task-Id: d94308d9-7655-4bac-8f15-c61478b5fca1

* chore: add changeset



Generated-By: PostHog Code
Task-Id: d94308d9-7655-4bac-8f15-c61478b5fca1

* refactor(spec): extract shared setup for group-context dedup tests into context block

* style: fix rubocop line length offenses in client_spec
gustavohstrassburger added a commit to PostHog/posthog-android that referenced this pull request May 27, 2026
…#533)

* fix(server): include group context in $feature_flag_called dedupe key

In `PostHogFeatureFlagCalledCache.add`, the per-`distinctId` LRU dedupe key only included `(distinctId, flagKey, value)`. For group-scoped flags this meant that when the same user was evaluated under a different group, no new `$feature_flag_called` event was fired — causing per-group exposure undercount for experiments scoped to a group key.

Add a canonical groups representation (`canonicalGroupsRepr`) into the LRU cache key — built from the sorted group entries so two equal maps with keys inserted in a different order produce the same dedup key. The `groups` map is now threaded through `PostHogStateless.captureFeatureFlagCalledEvent` and the `posthog-server` snapshot accessors (`PostHogFeatureFlagEvaluations.isEnabled/getFlag`) so it can be funneled into the dedup cache and onto the captured `$feature_flag_called` event payload.

This affects two modules:
- `posthog/` (PostHogStateless / cache) — `captureFeatureFlagCalledEvent` now takes an optional `groups` parameter (added via `@JvmOverloads` so the existing Java-callable signature still exists).
- `posthog-server/` — the snapshot carries the groups it was evaluated under and passes them to the host's dedup-aware capture call.

Mirrors the posthog-node fix in PostHog/posthog-js#3658 (which closes PostHog/posthog-js#3651). Both SDKs share the same dedupe shape, so backend evaluation needs the same change.

Generated-By: PostHog Code
Task-Id: d94308d9-7655-4bac-8f15-c61478b5fca1

* chore: add changeset



Generated-By: PostHog Code
Task-Id: d94308d9-7655-4bac-8f15-c61478b5fca1

* fix: escape entry separators in canonicalGroupsRepr

Two fixes from Greptile review on this PR:

1. Escape `=`, `;`, and `\` in `canonicalGroupsRepr` so that values containing the entry separators can't collide with synthetic decompositions of a different map. Previously, `mapOf("a" to "b;c=d", "e" to "f")` and `mapOf("a" to "b", "c" to "d", "e" to "f")` both serialized to `a=b;c=d;e=f;` and would have silently dedup-ed to one event.

2. Split the "null vs empty vs unset" dedup test into three independent caches, plus a new test that verifies values containing the separators hit distinct cache entries. Three-call-on-shared-cache assertions could pass purely from key-identity dedup instead of canonicalization parity.

Generated-By: PostHog Code
Task-Id: d94308d9-7655-4bac-8f15-c61478b5fca1

* chore: appease spotless formatting

spotlessKotlinCheck on CI flagged that `appendEscaped(sb: StringBuilder, value: String)` exceeds the project's per-line param width and needs to be broken across lines. Verified locally with `./gradlew spotlessKotlinCheck`.

Generated-By: PostHog Code
Task-Id: d94308d9-7655-4bac-8f15-c61478b5fca1

* fix(server): defensively copy groups map on construction
gustavohstrassburger added a commit to PostHog/posthog-rs that referenced this pull request May 28, 2026
* fix: include group context in $feature_flag_called dedupe key

In `build_dedup_key` (in both `client/async_client.rs` and `client/blocking.rs`), the per-`distinct_id` dedup key only included `(flag_key, response)`. For group-scoped flags this meant that when the same user was evaluated under a different group, no new `$feature_flag_called` event was fired — causing per-group exposure undercount for experiments scoped to a group key.

`build_dedup_key` now also takes the `groups` map; when non-empty it appends a canonical, sorted `"k1=v1;k2=v2;…"` suffix so the same `(user, flag, response)` fires once per distinct group context. Repeated calls under the same group context still dedupe; calls with the same group map in a different insertion order also dedupe (sorted via `sort_by(|a, b| a.0.cmp(b.0))`).

Mirrors the posthog-node fix in PostHog/posthog-js#3658 (which closes PostHog/posthog-js#3651). Both SDKs share the same dedupe shape, so backend evaluation needs the same change.

Generated-By: PostHog Code
Task-Id: d94308d9-7655-4bac-8f15-c61478b5fca1

* chore: add changeset



Generated-By: PostHog Code
Task-Id: d94308d9-7655-4bac-8f15-c61478b5fca1

* fix: percent-encode group keys/values in dedup key to prevent collisions

* chore: fix rustfmt formatting
gustavohstrassburger added a commit to PostHog/posthog-go that referenced this pull request May 28, 2026
* fix: include group context in $feature_flag_called dedupe key

In `captureFlagCalledIfNeeded`, the per-`distinct_id` LRU dedupe key only included `(distinct_id, flag_key, device_id)`. For group-scoped flags this meant that when the same user was evaluated under a different group, no new `$feature_flag_called` event was fired — causing per-group exposure undercount for experiments scoped to a group key.

Add the group context as a canonical JSON of the sorted group key/value pairs to the LRU cache key (new `groupsRepr` field on `flagUser`) so the same `(user, flag, device)` fires once per distinct group context. Repeated calls under the same group context still dedupe; calls with the same map but different Go map insertion order also dedupe (the JSON is built from a `sort.Strings`-sorted slice of keys).

Mirrors the posthog-node fix in PostHog/posthog-js#3658 (which closes PostHog/posthog-js#3651). Both SDKs share the same dedupe shape, so backend evaluation needs the same change.

Generated-By: PostHog Code
Task-Id: d94308d9-7655-4bac-8f15-c61478b5fca1

* chore: add changeset



Generated-By: PostHog Code
Task-Id: d94308d9-7655-4bac-8f15-c61478b5fca1

* test: combine flag-called dedup tests into table-driven test

Merge TestCaptureFlagCalled_DedupesAcrossRepeatedCallsUnderSameGroup
and TestCaptureFlagCalled_DedupesAcrossGroupKeyOrder into a single
parameterised test with a {name, calls []Groups} cases slice and
t.Run subtests, so the shared setup/sleep/assert boilerplate is
stated once.

Generated-By: PostHog Code
Task-Id: ad586036-ee97-4dc5-82e7-e96cb4fb9de7

* test: wait for async flush in dedup test to fix CI flake

The combined TestCaptureFlagCalled_DedupesAcrossSameGroupContext
relied on a 150ms sleep before asserting on captured events. On a
slow CI runner with -race enabled, the batch flush sometimes didn't
complete in time and the test saw zero events. Use the existing
waitForEventCount helper (already used by the sibling
TestCaptureFlagCalled_FiresPerGroupContext) to wait for the first
event with a real timeout, then sleep briefly to catch any
stragglers that would indicate broken deduplication, before
counting.

Generated-By: PostHog Code
Task-Id: ad586036-ee97-4dc5-82e7-e96cb4fb9de7
gustavohstrassburger added a commit to PostHog/posthog-dotnet that referenced this pull request May 28, 2026
* fix: include group context in $feature_flag_called dedupe key

In `PostHogClient.TryCaptureDedupedFeatureFlagCalledEvent`, the per-`distinctId` `MemoryCache` key only included `(distinctId, featureKey, cacheKeyValue)`. For group-scoped flags this meant that when the same user was evaluated under a different group, no new `$feature_flag_called` event was fired — causing per-group exposure undercount for experiments scoped to a group key.

Include a canonical representation of the `GroupCollection` in the cache key (new `CanonicalGroupsCacheKey` helper) so the same `(user, flag, response)` fires once per distinct group context. Repeated calls under the same group context still dedupe; group collections built in a different insertion order also dedupe (canonicalized via `OrderBy(GroupType, StringComparer.Ordinal)`).

Mirrors the posthog-node fix in PostHog/posthog-js#3658 (which closes PostHog/posthog-js#3651). Both SDKs share the same dedupe shape, so backend evaluation needs the same change.

Generated-By: PostHog Code
Task-Id: d94308d9-7655-4bac-8f15-c61478b5fca1

* chore: add changeset



Generated-By: PostHog Code
Task-Id: d94308d9-7655-4bac-8f15-c61478b5fca1

* refactor: address review nits on flag-called dedupe-groups

- Escape group type/key with Uri.EscapeDataString in CanonicalGroupsCacheKey
  to avoid `=`/`;` delimiter collisions.
- Drop the intermediate tuple Select and order Groups directly.
- Reword the dedupe-shape comment to not call the no-groups path "legacy".
- Match the factory comment to the actual variable names in the cache tuple.
- Collapse the three group-context dedupe Facts into one MemberData Theory.

Generated-By: PostHog Code
Task-Id: fe3e6da7-66a3-4fad-95bf-59d5df4557ee
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

$feature_flag_called dedupe does not include group context for group-scoped flags

2 participants