feat(flags): minimize $feature_flag_called events for non-experiment flags - #166
feat(flags): minimize $feature_flag_called events for non-experiment flags#166haacked wants to merge 2 commits into
Conversation
Emit a minimal, allowlisted $feature_flag_called event when the /flags response carries the top-level minimalFlagCalledEvents gate and the evaluated flag reports has_experiment: false. Any missing signal (gate absent or unparseable, has_experiment unknown, experiment-linked flag) keeps the full legacy event shape. The gate is stored on each FeatureFlags.Result so events fired later from an Evaluations snapshot respect it. Minimal events are assembled the same way capture/3 would (context, then global properties) and then projected onto the allowlist, so context tags and customer global properties are stripped while $groups and $process_person_profile survive when set. Generated-By: PostHog Code Task-Id: ffe402fd-d75c-4043-8e5d-d2fe513cac6f
posthog-elixir Compliance ReportDate: 2026-07-18 04:26:45 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
|
| :"$process_person_profile", | ||
| :"$lib", | ||
| :"$lib_version", | ||
| :"$is_server" |
There was a problem hiding this comment.
[P1] Minimal flag calls drop the session ID populated by the Plug integration.
PostHog.Integrations.Plug stores x-posthog-session-id in event context as $session_id. The minimal path merges that context before applying Map.take/2, but $session_id isn't in this allowlist, so gated non-experiment flag calls lose their session linkage.
Could we add $session_id to the atom/string allowlist and cover this through the Plug integration in a public-entry test, proving the session ID survives while unrelated request context remains stripped?
|
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 |
💡 Motivation and Context
$feature_flag_calledevents carry the full enriched properties (event context, global properties, system context) 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 (gate absent or unparseable,has_experimentunknown, experiment-linked flag) sends the full legacy shape unchanged.Server-gated because rolling this out unconditionally could break existing insights; announcement/comms come before enablement. The goal is to establish
$feature_flag_calledas a special system event.Part of a cross-SDK rollout; reference implementation and fuller context: PostHog/posthog-python#748.
How it works in this SDK
/flagsremote path applies: the gate is read from the top-levelminimalFlagCalledEventsfield of the v2 response.PostHog.FeatureFlags.Result(alongside the other response-level fields likerequest_idanderrors_while_computing), so events fired later from anEvaluationssnapshot respect it per flag.capture/3+bare_capture/4) and then projected onto the allowlist viaMap.takewith a dual atom/string key allowlist. Kept: flag identity and evaluation metadata ($feature_flag,$feature_flag_response,$feature_flag_has_experiment,$feature_flag_id,$feature_flag_version,$feature_flag_reason,$feature_flag_request_id,$feature_flag_evaluated_at,$feature_flag_errorwhen set),$groups,$process_person_profile,$lib,$lib_version, and$is_server. Everything else is stripped, including the$feature/<key>property, context tags, and customer global properties.before_sendstill runs after the projection and may re-inflate the event — the accepted customer escape hatch; the SDK itself adds nothing after the allowlist.capture_prepared/4that skips re-enrichment;bare_capture/4behavior is unchanged and the public API snapshot is untouched.💚 How did you test it?
mix test— 345 tests, 0 failures (18 integration-tagged excluded)mix format --check-formatted— cleanmix credo --strict— 0 issuesmix compile --warnings-as-errors— cleanmix posthog.public_api --check— snapshot up to dateNote: run locally on Elixir 1.18.3 / OTP 27.3.3 (
.tool-versionspins 1.20.0-otp-29, which wasn't installed locally); CI runs the pinned toolchain.Coverage added:
false/ unparseable ("yes") → full; gated buthas_experimentmissing → full.$is_server: true,$lib,$lib_version, and$feature_flag_errorwhen the response signals errors), so any extra or missing property fails the test.$groups/$process_person_profilesurvive the allowlist while string-keyed junk is stripped (pins the string half of the dual allowlist).Evaluationssnapshot flow: per-flag gate behavior from one snapshot (minimal for the non-experiment flag, full for the experiment flag) plus the missing-flag full-shape fallback.A minor changeset is included (
.sampo/changesets/mellow-ibex-sointu.md).📝 Checklist
If releasing new changes
sampo addto generate a changeset fileCreated with PostHog Code