fix: Dedupe feature flag called events by response#150
Conversation
posthog-elixir Compliance ReportDate: 2026-06-24 20:42:22 UTC
|
| Test | Status | Duration |
|---|---|---|
| Format Validation.Event Has Required Fields | ✅ | 610ms |
| Format Validation.Event Has Uuid | ✅ | 610ms |
| Format Validation.Event Has Lib Properties | ✅ | 610ms |
| Format Validation.Distinct Id Is String | ✅ | 609ms |
| Format Validation.Token Is Present | ✅ | 611ms |
| Format Validation.Custom Properties Preserved | ✅ | 610ms |
| Format Validation.Event Has Timestamp | ✅ | 610ms |
| Retry Behavior.Retries On 503 | ✅ | 5615ms |
| Retry Behavior.Does Not Retry On 400 | ✅ | 2613ms |
| Retry Behavior.Does Not Retry On 401 | ✅ | 2613ms |
| Retry Behavior.Respects Retry After Header | ✅ | 5615ms |
| Retry Behavior.Implements Backoff | ✅ | 15612ms |
| Retry Behavior.Retries On 500 | ✅ | 5616ms |
| Retry Behavior.Retries On 502 | ✅ | 5615ms |
| Retry Behavior.Retries On 504 | ✅ | 5615ms |
| Retry Behavior.Max Retries Respected | ✅ | 15617ms |
| Deduplication.Generates Unique Uuids | ✅ | 620ms |
| Deduplication.Preserves Uuid On Retry | ✅ | 5615ms |
| Deduplication.Preserves Uuid And Timestamp On Retry | ✅ | 10620ms |
| Deduplication.Preserves Uuid And Timestamp On Batch Retry | ✅ | 5617ms |
| Deduplication.No Duplicate Events In Batch | ✅ | 614ms |
| Deduplication.Different Events Have Different Uuids | ✅ | 612ms |
| Compression.Sends Gzip When Enabled | ✅ | 610ms |
| Batch Format.Uses Proper Batch Structure | ✅ | 609ms |
| Batch Format.Flush With No Events Sends Nothing | ✅ | 607ms |
| Batch Format.Multiple Events Batched Together | ✅ | 615ms |
| Error Handling.Does Not Retry On 403 | ✅ | 2610ms |
| Error Handling.Does Not Retry On 413 | ✅ | 2613ms |
| Error Handling.Retries On 408 | ✅ | 5616ms |
Feature_Flags Tests
View Details
| Test | Status | Duration |
|---|---|---|
| Request Payload.Request With Person Properties Device Id | ✅ | 8ms |
| Request Payload.Flags Request Uses V2 Query Param | ✅ | 7ms |
| Request Payload.Flags Request Hits Flags Path Not Decide | ✅ | 6ms |
| Request Payload.Flags Request Omits Authorization Header | ✅ | 6ms |
| Request Payload.Token In Flags Body Matches Init | ✅ | 6ms |
| Request Payload.Groups Round Trip | ✅ | 7ms |
| Request Payload.Groups Default To Empty Object | ✅ | 7ms |
| Request Payload.Person Properties Distinct Id Auto Populated When Caller Omits It | ✅ | 7ms |
| Request Payload.Disable Geoip False Propagates As Geoip Disable False | ✅ | 6ms |
| Request Payload.Disable Geoip Omitted Defaults To False | ❌ | 6ms |
| Request Payload.Flag Keys To Evaluate Contains Only Requested Key | ✅ | 7ms |
| Request Lifecycle.No Flags Request On Init Alone | ✅ | 3ms |
| Request Lifecycle.No Flags Request On Normal Capture | ✅ | 609ms |
| Request Lifecycle.Two Flag Calls Produce Two Remote Requests | ✅ | 10ms |
| Request Lifecycle.Mock Response Value Is Returned To Caller | ❌ | 7ms |
| Side Effect Events.Get Feature Flag Captures Feature Flag Called Event | ❌ | 609ms |
Failures
request_payload.disable_geoip_omitted_defaults_to_false
Field 'geoip_disable' not found in /flags request body at path 'geoip_disable'. Available keys: ['groups', 'api_key', 'distinct_id', 'flag_keys_to_evaluate', 'group_properties', 'person_properties']
request_lifecycle.mock_response_value_is_returned_to_caller
Last action result missing field 'value'. Keys: ['error', 'success']
side_effect_events.get_feature_flag_captures_feature_flag_called_event
Expected 1 events with name '$feature_flag_called', got 0
|
Reviews (1): Last reviewed commit: "fix: dedupe feature flag called events b..." | Re-trigger Greptile |
|
Using agent as a cache is a bit dangerous for production as that means all feature flag calls will be synchronously routed through a single process, creating a bottleneck. |
Mm good point, any suggestion? |
|
Usually an ETS table is used in some form with an attached process that manages it's lifecycle/eviction/etc. There's also a way to piggyback on the Registry for convenience. Not sure which way I would personally go, would likely need to spend some time exploring to get a feel for what works best. I can take a look at this if you're ok with waiting. |
| def first_seen?(supervisor_name, distinct_id, flag_key, value) do | ||
| key = {to_string(distinct_id), flag_key, value} |
There was a problem hiding this comment.
this doesn't account for groups in the cache key
There was a problem hiding this comment.
log_feature_flag_usage doesn't account for it either, but it should be able to be provided to FeatureFlags.check, get_feature_flag_result, evaluate_flags via the body
There was a problem hiding this comment.
yeah groups aren't accounted for in a few SDKs, i think i'd do this as a follow up since i can check/update the spec and do all at once later
|
@martosaur sounds good, not in a rush |
merged this but happy to hear if theres a better way, we can fix forward. |
💡 Motivation and Context
Repeated feature flag reads currently emit duplicate
$feature_flag_calledevents for the same distinct ID, flag, and returned response value. This adds per-supervisor deduplication so duplicate reads for the same response are suppressed while still emitting again when the response changes.💚 How did you test it?
mix format --check-formattedmix test test/posthog/feature_flags/evaluations_test.exsmix test📝 Checklist
If releasing new changes
sampo addto generate a changeset file🤖 Agent context
Autonomy: Human-driven (agent-assisted)
A coding agent prepared this PR from the dedicated
fix/ff-called-value-dedupeworktree at the user's request. The implementation keeps deduplication scoped to the PostHog supervisor by registering an Agent-backed cache under the existing registry, suppresses repeated$feature_flag_calledcaptures for the same distinct ID/flag/response tuple, preserves no-op behavior when no supervisor is running, and adds tests for same-value dedupe and changed-value recapture.