fix(flags): keep session attribution on minimized $feature_flag_called events - #252
posthog[bot] wants to merge 1 commit into
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-03T06:08:54.884962+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-03T06:09:04.001378+00:00
|
| Test | Status | Duration |
|---|---|---|
| Format Validation.Event Has Required Fields | ✅ | 32ms |
| Format Validation.Event Has Uuid | ✅ | 527ms |
| Format Validation.Event Has Lib Properties | ✅ | 529ms |
| Format Validation.Distinct Id Is String | ✅ | 529ms |
| Format Validation.Token Is Present | ✅ | 528ms |
| Format Validation.Custom Properties Preserved | ✅ | 530ms |
| Format Validation.Event Has Timestamp | ✅ | 528ms |
| Format Validation.Non Utc Event Timestamp Is Converted To Utc | ✅ | 530ms |
| Retry Behavior.Retries On 503 | ❌ | 5531ms |
| Retry Behavior.Does Not Retry On 400 | ✅ | 2532ms |
| Retry Behavior.Does Not Retry On 401 | ✅ | 2534ms |
| Retry Behavior.Respects Retry After Header | ❌ | 5536ms |
| Retry Behavior.Implements Backoff | ❌ | 15543ms |
| Retry Behavior.Retries On 500 | ❌ | 5537ms |
| Retry Behavior.Retries On 502 | ❌ | 5533ms |
| Retry Behavior.Retries On 504 | ❌ | 5537ms |
| Retry Behavior.Max Retries Respected | ❌ | 15545ms |
| Deduplication.Generates Unique Uuids | ✅ | 536ms |
| Deduplication.Preserves Uuid On Retry | ❌ | 5532ms |
| Deduplication.Preserves Uuid And Timestamp On Retry | ❌ | 10540ms |
| Deduplication.Preserves Uuid And Timestamp On Batch Retry | ❌ | 5539ms |
| Deduplication.No Duplicate Events In Batch | ✅ | 536ms |
| Deduplication.Different Events Have Different Uuids | ✅ | 530ms |
| Compression.Sends Gzip When Enabled | ✅ | 531ms |
| Batch Format.Uses Proper Batch Structure | ✅ | 529ms |
| Batch Format.Flush With No Events Sends Nothing | ✅ | 517ms |
| Batch Format.Multiple Events Batched Together | ✅ | 519ms |
| Error Handling.Does Not Retry On 403 | ✅ | 2531ms |
| Error Handling.Does Not Retry On 413 | ✅ | 2531ms |
| Error Handling.Retries On 408 | ❌ | 5535ms |
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 | ✅ | 520ms |
| Request Payload.Flags Request Uses V2 Query Param | ✅ | 520ms |
| Request Payload.Flags Request Hits Flags Path Not Decide | ✅ | 521ms |
| Request Payload.Flags Request Omits Authorization Header | ✅ | 520ms |
| Request Payload.Token In Flags Body Matches Init | ✅ | 521ms |
| Request Payload.Groups Round Trip | ✅ | 521ms |
| Request Payload.Groups Default To Empty Object | ✅ | 521ms |
| Request Payload.Disable Geoip False Propagates As Geoip Disable False | ✅ | 519ms |
| Request Payload.Disable Geoip Omitted Defaults To False | ✅ | 521ms |
| Request Payload.Flag Keys To Evaluate Contains Only Requested Key | ✅ | 521ms |
| Request Lifecycle.No Flags Request On Init Alone | ✅ | 515ms |
| Request Lifecycle.No Flags Request On Normal Capture | ✅ | 515ms |
| Request Lifecycle.Two Flag Calls Produce Two Remote Requests | ✅ | 525ms |
| Request Lifecycle.Mock Response Value Is Returned To Caller | ✅ | 521ms |
| Retry Behavior.Retries Flags On 502 | ✅ | 623ms |
| Retry Behavior.Retries Flags On 504 | ✅ | 623ms |
| Side Effect Events.Get Feature Flag Captures Feature Flag Called Event | ✅ | 531ms |
posthog-php-socket Compliance ReportDate: 2026-10-03T06:09:23.461943+00:00
|
| Test | Status | Duration |
|---|---|---|
| Format Validation.Event Has Required Fields | ✅ | 28ms |
| Format Validation.Event Has Uuid | ✅ | 522ms |
| Format Validation.Event Has Lib Properties | ✅ | 524ms |
| Format Validation.Distinct Id Is String | ✅ | 524ms |
| Format Validation.Token Is Present | ✅ | 524ms |
| Format Validation.Custom Properties Preserved | ✅ | 525ms |
| Format Validation.Event Has Timestamp | ✅ | 523ms |
| Format Validation.Non Utc Event Timestamp Is Converted To Utc | ✅ | 525ms |
| Retry Behavior.Retries On 503 | ❌ | 9233ms |
| Retry Behavior.Does Not Retry On 400 | ✅ | 2528ms |
| Retry Behavior.Does Not Retry On 401 | ✅ | 2527ms |
| Retry Behavior.Respects Retry After Header | ❌ | 9234ms |
| Retry Behavior.Implements Backoff | ❌ | 19231ms |
| Retry Behavior.Retries On 500 | ❌ | 8752ms |
| Retry Behavior.Retries On 502 | ❌ | 9236ms |
| Retry Behavior.Retries On 504 | ❌ | 9236ms |
| Retry Behavior.Max Retries Respected | ❌ | 19246ms |
| Deduplication.Generates Unique Uuids | ✅ | 534ms |
| Deduplication.Preserves Uuid On Retry | ❌ | 9234ms |
| Deduplication.Preserves Uuid And Timestamp On Retry | ❌ | 14241ms |
| Deduplication.Preserves Uuid And Timestamp On Batch Retry | ❌ | 9239ms |
| Deduplication.No Duplicate Events In Batch | ✅ | 532ms |
| Deduplication.Different Events Have Different Uuids | ✅ | 528ms |
| Compression.Sends Gzip When Enabled | ✅ | 524ms |
| Batch Format.Uses Proper Batch Structure | ✅ | 524ms |
| Batch Format.Flush With No Events Sends Nothing | ✅ | 520ms |
| Batch Format.Multiple Events Batched Together | ✅ | 512ms |
| Error Handling.Does Not Retry On 403 | ✅ | 2527ms |
| Error Handling.Does Not Retry On 413 | ✅ | 2526ms |
| Error Handling.Retries On 408 | ❌ | 5528ms |
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 | ✅ | 522ms |
| 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 | ✅ | 522ms |
| Request Payload.Groups Round Trip | ✅ | 522ms |
| Request Payload.Groups Default To Empty Object | ✅ | 522ms |
| Request Payload.Disable Geoip False Propagates As Geoip Disable False | ✅ | 521ms |
| Request Payload.Disable Geoip Omitted Defaults To False | ✅ | 523ms |
| Request Payload.Flag Keys To Evaluate Contains Only Requested Key | ✅ | 522ms |
| 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 | ✅ | 523ms |
| Retry Behavior.Retries Flags On 502 | ✅ | 626ms |
| Retry Behavior.Retries Flags On 504 | ✅ | 625ms |
| Side Effect Events.Get Feature Flag Captures Feature Flag Called Event | ✅ | 524ms |
💡 Motivation and Context
Brings
posthog-phpinto compliance with the cross-SDKfeature-flag-called-trackerspec, which requires the minimal-event allowlist to cover session-attribution properties:The SDK compliance matrix records this as a 🟡 Partial for posthog-php:
In PHP this is reachable today: an app that puts attribution onto the request context (
PostHog::withContext(['properties' => ['utm_source' => ...]]), the documented framework-middleware pattern) has those values stripped from a gated$feature_flag_calledevent. When that event is a session's first event, the whole session loses its attribution in web analytics.Explanation of the change
Ten keys added to
Client::MINIMAL_FLAG_CALLED_EVENT_PROPERTIES, plus a comment explaining why they are there and why$referreris not. No other behavior changes: the gating rules (serverminimalFlagCalledEventsgate and an explicithas_experiment === false), the dedupe key, and the fail-safe-to-full-event path are untouched.Scope is limited to this one contract — the matrix's other open gaps for this SDK are left alone.
Why this is backwards-compatible
Strictly additive. Widening a strip-allowlist can only cause a minimized event to retain more properties than before, never fewer. Unminimized events already carried these properties and are unaffected, and no public API changes (
php scripts/check-public-api.phpreports the snapshot is up to date — the constant is private).💚 How did you test it?
New test
testMinimalFlagCalledEventKeepsSessionAttributionPropertiesintest/FeatureFlagTest.php: sets all ten attribution properties plus$referrerand an unrelated custom property on the request context, triggers a gated minimized$feature_flag_calledevent, and asserts the ten survive while$referrer, the custom property, and$lib_consumerare still stripped. Verified it fails onmainand passes with the change.Locally, on PHP 8.3:
vendor/bin/phpunit— 732 tests, 5403 assertions, 0 failuresvendor/bin/phpcs lib/ test/— no new offenses (the one pre-existing line-length warning intest/assests/MockedResponses.phpis untouched)php scripts/check-public-api.php— snapshot up to dateFollow-up work
register()), so today these values only reach an event via the request context or explicit per-call properties. Ifregister()ever lands, the allowlist is already correct.📝 Checklist
If releasing new changes
pnpm changeto generate a change intent file🤖 Agent context
Autonomy: Fully autonomous
Opened by the scheduled SDK-compliance workflow, which reads the compliance matrices in PostHog/sdk-specs and implements one backwards-compatible gap per run. Tools used: Claude Code (Bash, file edit,
gh, PHPUnit, PHP CodeSniffer).Selection notes for reviewers: posthog-php's
Is Feature Enabled❌ gap scored higher on paper, but #216 already proposed that exact change and was closed by a maintainer without comment, so it was deliberately skipped rather than re-opened — it needs a human decision, not another PR. This allowlist gap was chosen instead as strictly additive with no rejection history. The deprecatedClient::isFeatureEnabled()was not touched.Created with PostHog Desktop
🤖 Generated with Claude Code