Repository navigation
fix(flags): keep session attribution on minimized $feature_flag_called events - #252
Conversation
…d events Add $referring_domain, utm_source, utm_medium, utm_campaign, utm_content, utm_term, gad_source, mc_cid, gclid and fbclid to the minimal-event allowlist, per the feature-flag-called-tracker spec. Web analytics reads a session's initial attribution from the session's first event, and a minimized $feature_flag_called event can be that first event, so stripping these nulled out attribution for the whole session. Full $referrer stays excluded. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Generated-By: PostHog Desktop Task-Id: 9910bd77-fd45-4b67-8a88-a03de312b2ca
posthog-php-lib_curl Compliance ReportDate: 2026-10-09T10:24:28.596511+00:00 ✅ All Tests Passed!47/47 tests passed Capture Tests✅ 30/30 tests passed View Details
Feature_Flags Tests✅ 17/17 tests passed View Details
|
posthog-php-fork_curl Compliance ReportDate: 2026-10-09T10:24:37.104587+00:00
|
| Test | Status | Duration |
|---|---|---|
| Format Validation.Event Has Required Fields | ✅ | 34ms |
| Format Validation.Event Has Uuid | ✅ | 528ms |
| Format Validation.Event Has Lib Properties | ✅ | 528ms |
| Format Validation.Distinct Id Is String | ✅ | 528ms |
| Format Validation.Token Is Present | ✅ | 528ms |
| Format Validation.Custom Properties Preserved | ✅ | 528ms |
| Format Validation.Event Has Timestamp | ✅ | 528ms |
| Format Validation.Non Utc Event Timestamp Is Converted To Utc | ✅ | 527ms |
| Retry Behavior.Retries On 503 | ❌ | 5531ms |
| Retry Behavior.Does Not Retry On 400 | ✅ | 2533ms |
| Retry Behavior.Does Not Retry On 401 | ✅ | 2531ms |
| Retry Behavior.Respects Retry After Header | ❌ | 5536ms |
| Retry Behavior.Implements Backoff | ❌ | 15544ms |
| Retry Behavior.Retries On 500 | ❌ | 5535ms |
| Retry Behavior.Retries On 502 | ❌ | 5534ms |
| Retry Behavior.Retries On 504 | ❌ | 5538ms |
| Retry Behavior.Max Retries Respected | ❌ | 15544ms |
| Deduplication.Generates Unique Uuids | ✅ | 540ms |
| Deduplication.Preserves Uuid On Retry | ❌ | 5532ms |
| Deduplication.Preserves Uuid And Timestamp On Retry | ❌ | 10539ms |
| Deduplication.Preserves Uuid And Timestamp On Batch Retry | ❌ | 5540ms |
| Deduplication.No Duplicate Events In Batch | ✅ | 535ms |
| Deduplication.Different Events Have Different Uuids | ✅ | 530ms |
| Compression.Sends Gzip When Enabled | ✅ | 531ms |
| Batch Format.Uses Proper Batch Structure | ✅ | 528ms |
| Batch Format.Flush With No Events Sends Nothing | ✅ | 517ms |
| Batch Format.Multiple Events Batched Together | ✅ | 520ms |
| Error Handling.Does Not Retry On 403 | ✅ | 2529ms |
| Error Handling.Does Not Retry On 413 | ✅ | 2531ms |
| Error Handling.Retries On 408 | ❌ | 5532ms |
Failures
retry_behavior.retries_on_503
Expected at least 3 requests, got 1
retry_behavior.respects_retry_after_header
Expected at least 2 requests, got 1
retry_behavior.implements_backoff
Expected at least 3 requests, got 1
retry_behavior.retries_on_500
Expected at least 2 requests, got 1
retry_behavior.retries_on_502
Expected at least 2 requests, got 1
retry_behavior.retries_on_504
Expected at least 2 requests, got 1
retry_behavior.max_retries_respected
Expected 4 requests, got 1
deduplication.preserves_uuid_on_retry
Need at least 2 requests to check retry
deduplication.preserves_uuid_and_timestamp_on_retry
Expected at least 3 requests, got 1
deduplication.preserves_uuid_and_timestamp_on_batch_retry
Expected at least 2 requests, got 1
error_handling.retries_on_408
Expected at least 2 requests, got 1
Feature_Flags Tests
✅ 17/17 tests passed
View Details
| Test | Status | Duration |
|---|---|---|
| Request Payload.Request With Person Properties Device Id | ✅ | 524ms |
| Request Payload.Flags Request Uses V2 Query Param | ✅ | 520ms |
| Request Payload.Flags Request Hits Flags Path Not Decide | ✅ | 520ms |
| Request Payload.Flags Request Omits Authorization Header | ✅ | 521ms |
| Request Payload.Token In Flags Body Matches Init | ✅ | 519ms |
| Request Payload.Groups Round Trip | ✅ | 519ms |
| Request Payload.Groups Default To Empty Object | ✅ | 521ms |
| Request Payload.Disable Geoip False Propagates As Geoip Disable False | ✅ | 522ms |
| Request Payload.Disable Geoip Omitted Defaults To False | ✅ | 521ms |
| Request Payload.Flag Keys To Evaluate Contains Only Requested Key | ✅ | 520ms |
| Request Lifecycle.No Flags Request On Init Alone | ✅ | 515ms |
| Request Lifecycle.No Flags Request On Normal Capture | ✅ | 518ms |
| Request Lifecycle.Two Flag Calls Produce Two Remote Requests | ✅ | 525ms |
| Request Lifecycle.Mock Response Value Is Returned To Caller | ✅ | 522ms |
| Retry Behavior.Retries Flags On 502 | ✅ | 623ms |
| Retry Behavior.Retries Flags On 504 | ✅ | 625ms |
| Side Effect Events.Get Feature Flag Captures Feature Flag Called Event | ✅ | 531ms |
posthog-php-socket Compliance ReportDate: 2026-10-09T10:25:03.566423+00:00
|
| Test | Status | Duration |
|---|---|---|
| Format Validation.Event Has Required Fields | ✅ | 31ms |
| Format Validation.Event Has Uuid | ✅ | 524ms |
| Format Validation.Event Has Lib Properties | ✅ | 524ms |
| Format Validation.Distinct Id Is String | ✅ | 525ms |
| Format Validation.Token Is Present | ✅ | 525ms |
| Format Validation.Custom Properties Preserved | ✅ | 525ms |
| Format Validation.Event Has Timestamp | ✅ | 524ms |
| Format Validation.Non Utc Event Timestamp Is Converted To Utc | ✅ | 526ms |
| Retry Behavior.Retries On 503 | ❌ | 9234ms |
| Retry Behavior.Does Not Retry On 400 | ✅ | 2528ms |
| Retry Behavior.Does Not Retry On 401 | ✅ | 2529ms |
| Retry Behavior.Respects Retry After Header | ❌ | 9235ms |
| Retry Behavior.Implements Backoff | ❌ | 19234ms |
| Retry Behavior.Retries On 500 | ❌ | 8751ms |
| Retry Behavior.Retries On 502 | ❌ | 9237ms |
| Retry Behavior.Retries On 504 | ❌ | 9237ms |
| Retry Behavior.Max Retries Respected | ❌ | 19247ms |
| Deduplication.Generates Unique Uuids | ✅ | 534ms |
| Deduplication.Preserves Uuid On Retry | ❌ | 9235ms |
| Deduplication.Preserves Uuid And Timestamp On Retry | ❌ | 14241ms |
| Deduplication.Preserves Uuid And Timestamp On Batch Retry | ❌ | 9240ms |
| Deduplication.No Duplicate Events In Batch | ✅ | 532ms |
| Deduplication.Different Events Have Different Uuids | ✅ | 526ms |
| Compression.Sends Gzip When Enabled | ✅ | 525ms |
| Batch Format.Uses Proper Batch Structure | ✅ | 525ms |
| Batch Format.Flush With No Events Sends Nothing | ✅ | 521ms |
| Batch Format.Multiple Events Batched Together | ✅ | 512ms |
| Error Handling.Does Not Retry On 403 | ✅ | 2527ms |
| Error Handling.Does Not Retry On 413 | ✅ | 2528ms |
| Error Handling.Retries On 408 | ❌ | 5530ms |
Failures
retry_behavior.retries_on_503
Expected at least 3 requests, got 1
retry_behavior.respects_retry_after_header
Expected at least 2 requests, got 1
retry_behavior.implements_backoff
Expected at least 3 requests, got 1
retry_behavior.retries_on_500
Expected at least 2 requests, got 1
retry_behavior.retries_on_502
Expected at least 2 requests, got 1
retry_behavior.retries_on_504
Expected at least 2 requests, got 1
retry_behavior.max_retries_respected
Expected 4 requests, got 1
deduplication.preserves_uuid_on_retry
Need at least 2 requests to check retry
deduplication.preserves_uuid_and_timestamp_on_retry
Expected at least 3 requests, got 1
deduplication.preserves_uuid_and_timestamp_on_batch_retry
Expected at least 2 requests, got 1
error_handling.retries_on_408
Expected at least 2 requests, got 1
Feature_Flags Tests
✅ 17/17 tests passed
View Details
| Test | Status | Duration |
|---|---|---|
| Request Payload.Request With Person Properties Device Id | ✅ | 525ms |
| Request Payload.Flags Request Uses V2 Query Param | ✅ | 523ms |
| Request Payload.Flags Request Hits Flags Path Not Decide | ✅ | 522ms |
| Request Payload.Flags Request Omits Authorization Header | ✅ | 523ms |
| Request Payload.Token In Flags Body Matches Init | ✅ | 523ms |
| Request Payload.Groups Round Trip | ✅ | 523ms |
| Request Payload.Groups Default To Empty Object | ✅ | 522ms |
| Request Payload.Disable Geoip False Propagates As Geoip Disable False | ✅ | 523ms |
| Request Payload.Disable Geoip Omitted Defaults To False | ✅ | 523ms |
| Request Payload.Flag Keys To Evaluate Contains Only Requested Key | ✅ | 523ms |
| Request Lifecycle.No Flags Request On Init Alone | ✅ | 518ms |
| Request Lifecycle.No Flags Request On Normal Capture | ✅ | 509ms |
| Request Lifecycle.Two Flag Calls Produce Two Remote Requests | ✅ | 527ms |
| Request Lifecycle.Mock Response Value Is Returned To Caller | ✅ | 524ms |
| Retry Behavior.Retries Flags On 502 | ✅ | 625ms |
| Retry Behavior.Retries Flags On 504 | ✅ | 626ms |
| Side Effect Events.Get Feature Flag Captures Feature Flag Called Event | ✅ | 525ms |
There was a problem hiding this comment.
🤖 An AI agent wrote this on behalf of @ioannisj.
Premise: this matches the sdk-specs PHP gap and the posthog-js shape for keeping session-attribution on minimized $feature_flag_called events while still excluding $referrer.
Adding the full js campaign click-id list before approving.
… flag-called events
Pushed cb8c70d to match the posthog-js click-id list ( I left |
dustinbyrne
left a comment
There was a problem hiding this comment.
seems reasonable though very edge case-y as i don't think UTM parameters are included anywhere unless user specified
| * stays out; only $referring_domain and the bare campaign/click-id keys survive. The | ||
| * campaign/click-id keys mirror posthog-js MINIMAL_FLAG_CALLED_EVENT_CAMPAIGN_PROPERTIES. | ||
| */ | ||
| private const MINIMAL_FLAG_CALLED_EVENT_PROPERTIES = [ |
There was a problem hiding this comment.
we reallocate this array on every event, can consider switching it to a map (apparently PHP doesn't have sets) to avoid calling array_flip() and reallocating
private const MINIMAL_FLAG_CALLED_EVENT_PROPERTIES = [
/* ... */
'$feature_flag' => true,
'utm_source' => true,
];
💡 Motivation and Context
The
feature-flag-called-trackerspec requires minimized$feature_flag_calledevents to keep session-attribution properties. Web analytics reads a session's attribution from its first event, and a minimized event can be that first event. The sdk-specs compliance matrix flags posthog-php as missing all of them.This is reachable in PHP today: attribution set via
PostHog::withContext(['properties' => ['utm_source' => ...]])gets stripped from a gated$feature_flag_calledevent, nulling the whole session's attribution.Adds to
Client::MINIMAL_FLAG_CALLED_EVENT_PROPERTIES:$referring_domain, the fiveutm_*params,gad_sourceandmc_cid, as named by the specMINIMAL_FLAG_CALLED_EVENT_CAMPAIGN_PROPERTIES(packages/core/src/featureFlagUtils.ts), in the same order:gclid,gclsrc,dclid,gbraid,wbraid,fbclid,msclkid,twclid,li_fat_id,igshid,ttclid,rdt_cid,epik,qclid,sccid,oppref,irclid,_kxFull
$referrerstays excluded. The change is strictly additive: minimized events can only keep more properties, and the constant is private so there's no public API change. Gating, dedupe and the fail-safe-to-full-event path are untouched.Follow-ups:
$current_url,$pathname,$window_idand$device_id. They're left out because the spec doesn't require them and they're mostly client-side.💚 How did you test it?
testMinimalFlagCalledEventKeepsSessionAttributionPropertiesintest/FeatureFlagTest.phpsets the spec-named attribution keys,msclkid,ttclid,_kx,$referrerand a custom property on the request context. It asserts the attribution keys survive a gated minimized event while$referrer, the custom property and$lib_consumerare stripped.The full suite,
phpcsandscripts/check-public-api.phppassed on the original commit. After adding the click-id list,test/FeatureFlagTest.phppasses (70 tests).📝 Checklist
If releasing new changes
pnpm changeto generate a change intent file🤖 Agent context
DRI: @ioannisj
Autonomy: Human-driven (agent-assisted)