Skip to content

fix(flags): keep session attribution on minimized $feature_flag_called events - #252

Draft
posthog[bot] wants to merge 1 commit into
mainfrom
posthog/flag-called-session-attribution-allowlist
Draft

posthog[bot] wants to merge 1 commit into
mainfrom
posthog/flag-called-session-attribution-allowlist

Conversation

@posthog

@posthog posthog Bot commented Oct 3, 2026

Copy link
Copy Markdown

💡 Motivation and Context

Brings posthog-php into compliance with the cross-SDK feature-flag-called-tracker spec, which requires the minimal-event allowlist to cover session-attribution properties:

Session-attribution properties — $referring_domain and the canonical campaign/click-id params the SDK already registers as super properties (e.g. utm_source, utm_medium, utm_campaign, utm_content, utm_term, gad_source, mc_cid, and click-id params such as gclid/fbclid). Web-analytics session-initial attribution (UTM values, channel type) is read from the first event in a session, and a minimized event can be that first event; stripping these would silently null out the whole session's attribution. Full $referrer remains excluded — only $referring_domain and the bare campaign-param keys survive minimization.

The SDK compliance matrix records this as a 🟡 Partial for posthog-php:

Client::MINIMAL_FLAG_CALLED_EVENT_PROPERTIES contains the flag-identity/debug/session/platform categories, but zero session-attribution properties. It is missing all 10 of $referring_domain, utm_source, utm_medium, utm_campaign, utm_content, utm_term, gad_source, mc_cid, gclid, fbclid — the exact same gap class independently found in posthog-python and posthog-node. Everything else matches the spec precisely and is well-tested.

Remediation: Add the 10 session-attribution property names to Client::MINIMAL_FLAG_CALLED_EVENT_PROPERTIES.

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_called event. 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 $referrer is not. No other behavior changes: the gating rules (server minimalFlagCalledEvents gate and an explicit has_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.php reports the snapshot is up to date — the constant is private).

💚 How did you test it?

New test testMinimalFlagCalledEventKeepsSessionAttributionProperties in test/FeatureFlagTest.php: sets all ten attribution properties plus $referrer and an unrelated custom property on the request context, triggers a gated minimized $feature_flag_called event, and asserts the ten survive while $referrer, the custom property, and $lib_consumer are still stripped. Verified it fails on main and passes with the change.

Locally, on PHP 8.3:

  • vendor/bin/phpunit — 732 tests, 5403 assertions, 0 failures
  • vendor/bin/phpcs lib/ test/ — no new offenses (the one pre-existing line-length warning in test/assests/MockedResponses.php is untouched)
  • php scripts/check-public-api.php — snapshot up to date

Follow-up work

  • The same session-attribution allowlist gap is recorded for posthog-python and posthog-node.
  • PHP has no super-property registration (register()), so today these values only reach an event via the request context or explicit per-call properties. If register() ever lands, the allowlist is already correct.

📝 Checklist

  • I reviewed the submitted code.
  • I added tests to verify the changes.
  • I updated the docs if needed.
  • No breaking change or entry added to the changelog.

If releasing new changes

  • Ran pnpm change to 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 deprecated Client::isFeatureEnabled() was not touched.


Created with PostHog Desktop

🤖 Generated with Claude Code

…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
@github-actions

github-actions Bot commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

posthog-php-lib_curl Compliance Report

Date: 2026-10-03T06:08:54.884962+00:00
Duration: 118379ms

✅ All Tests Passed!

47/47 tests passed


Capture Tests

✅ 30/30 tests passed

View Details
Test Status Duration
Format Validation.Event Has Required Fields ✅ 28ms
Format Validation.Event Has Uuid ✅ 523ms
Format Validation.Event Has Lib Properties ✅ 525ms
Format Validation.Distinct Id Is String ✅ 524ms
Format Validation.Token Is Present ✅ 524ms
Format Validation.Custom Properties Preserved ✅ 524ms
Format Validation.Event Has Timestamp ✅ 523ms
Format Validation.Non Utc Event Timestamp Is Converted To Utc ✅ 527ms
Retry Behavior.Retries On 503 ✅ 5832ms
Retry Behavior.Does Not Retry On 400 ✅ 2528ms
Retry Behavior.Does Not Retry On 401 ✅ 2528ms
Retry Behavior.Respects Retry After Header ✅ 8533ms
Retry Behavior.Implements Backoff ✅ 16248ms
Retry Behavior.Retries On 500 ✅ 5634ms
Retry Behavior.Retries On 502 ✅ 5634ms
Retry Behavior.Retries On 504 ✅ 5632ms
Retry Behavior.Max Retries Respected ✅ 17049ms
Deduplication.Generates Unique Uuids ✅ 233ms
Deduplication.Preserves Uuid On Retry ✅ 5632ms
Deduplication.Preserves Uuid And Timestamp On Retry ✅ 10830ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry ✅ 5144ms
Deduplication.No Duplicate Events In Batch ✅ 530ms
Deduplication.Different Events Have Different Uuids ✅ 525ms
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 ✅ 514ms
Error Handling.Does Not Retry On 403 ✅ 2525ms
Error Handling.Does Not Retry On 413 ✅ 2528ms
Error Handling.Retries On 408 ✅ 5633ms

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 ✅ 523ms
Request Payload.Flags Request Omits Authorization Header ✅ 521ms
Request Payload.Token In Flags Body Matches Init ✅ 522ms
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 ✅ 517ms
Request Lifecycle.No Flags Request On Normal Capture ✅ 511ms
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 ✅ 626ms
Retry Behavior.Retries Flags On 504 ✅ 624ms
Side Effect Events.Get Feature Flag Captures Feature Flag Called Event ✅ 526ms

@github-actions

github-actions Bot commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

posthog-php-fork_curl Compliance Report

Date: 2026-10-03T06:09:04.001378+00:00
Duration: 112820ms

⚠️ Some Tests Failed

36/47 tests passed, 11 failed


Capture Tests

⚠️ 19/30 tests passed, 11 failed

View Details
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

@github-actions

github-actions Bot commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

posthog-php-socket Compliance Report

Date: 2026-10-03T06:09:23.461943+00:00
Duration: 149247ms

⚠️ Some Tests Failed

36/47 tests passed, 11 failed


Capture Tests

⚠️ 19/30 tests passed, 11 failed

View Details
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

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants