feat(flags): minimize $feature_flag_called events for non-experiment flags - #627
Conversation
When the v2 /flags response carries top-level minimalFlagCalledEvents: true and the evaluated flag's has_experiment metadata is false, reduce the $feature_flag_called event properties to the cross-SDK strict allowlist, stripping registered super properties, the static/dynamic context envelope, the $feature/<key> enumeration, and $active_feature_flags. Any missing signal (field absent, legacy response shape, cached state without it, has_experiment unknown) keeps the full legacy shape, and experiment-linked flags always send the full envelope. The gate is persisted alongside the cached flags so it survives app restarts. Generated-By: PostHog Code Task-Id: ffe402fd-d75c-4043-8e5d-d2fe513cac6f
posthog-android Compliance ReportDate: 2026-07-31 00:38:00 UTC ✅ All Tests Passed!46/46 tests passed Capture Tests✅ 29/29 tests passed View Details
Feature_Flags Tests✅ 17/17 tests passed View Details
|
Correct a comment in PostHogStateless.kt that claimed the minimal-events allowlist lists only properties this SDK sets today. Add a test for the untested has_experiment-absent branch under the gate, and a test locking in that bootstrap-provenance properties are stripped from minimized events. Simplify the allowlist filter loop to filterKeys.
| override fun isMinimalFlagCalledEventsEnabled(): Boolean { | ||
| return remoteConfig?.isMinimalFlagCalledEventsEnabled() == true | ||
| } |
There was a problem hiding this comment.
this definition would enable minimal flag called events for Android - should posthog-android be listed in the changeset?
or is there something else missing?
There was a problem hiding this comment.
Yep, posthog-android belongs in there. Added it.
Nothing is missing on the Android side. PostHogAndroid.setup() builds the core PostHog, so this override is the Android implementation, and PostHogSharedPreferences filters on the shared ALL_INTERNAL_KEYS, which already includes the new preference key.
The gap is in releasing. posthog-android re-exports core via api(project(":posthog")) and pins the core version in its published POM, so shipping core alone would leave Android consumers on the old core. That's what forced the re-pin releases in #611 and #625. Bumped it as a minor rather than a patch since the event shape changes for Android users.
Left posthog-server out: its PostHogFeatureFlags doesn't override isMinimalFlagCalledEventsEnabled(), so it keeps the false default and nothing changes for it.
|
This PR hasn't seen activity in a week! Should it be merged, closed, or further worked on? If you want to keep it open, post a comment or remove the |
posthog-android re-exports the core via api(project(":posthog")) and pins
the core version in its published POM, so releasing posthog alone leaves
Android consumers on the old core.
Claude-Session: https://claude.ai/code/session_01DphJuWo8DxpKNodU8Lq1cR
💡 Motivation and Context
$feature_flag_calledevents carry the full enriched properties (context envelope, super properties,$active_feature_flags, the$feature/<key>enumeration) on every flag call, even for flags not linked to an experiment. This PR trims those events to a strict allowlist iff the server-controlled gate is on (top-levelminimalFlagCalledEventsin the v2/flagsresponse) AND the flag'shas_experimentis exactlyfalse. Any missing signal (field absent, legacy response shape, cached state without it,has_experimentunknown) sends the full legacy shape unchanged, and experiment-linked flags always send the full envelope. Server-gated because rolling this out unconditionally could break existing insights; announcement/comms come before enablement.Implementation notes:
posthogcore module.ALL_INTERNAL_KEYS, so it cannot leak into events via registered super properties.reset()/identity change.captureStateless) after property assembly, building the minimal property set from the allowlist rather than deleting from the full set.has_experiment, so events stay full until the first fresh/flagsresponse of the session (fail-safe, bounded staleness).Part of a cross-SDK rollout; reference implementation and fuller context: PostHog/posthog-python#748
💚 How did you test it?
Ran locally:
./gradlew :posthog:test— 805 tests including the new ones: gated + no experiment → exact allowlist key set (super props, context envelope,$feature/<key>,$active_feature_flagsstripped); gated + experiment → full; ungated / field absent → full; gate + minimal shape persist across a simulated SDK restart; gate parse/persist/flip-off/clear at thePostHogRemoteConfiglevel../gradlew :posthog-server:test./gradlew spotlessCheck./gradlew :posthog:apiCheck :posthog-server:apiCheck(afterapiDump)Could not run
:posthog-android:testDebugUnitTestor Android lint in the authoring environment (no Android SDK available) — no Android-module sources changed, but CI must confirm.📝 Checklist
If releasing new changes
pnpm changesetto generate a changeset fileCreated with PostHog Code