Skip to content

fix(flags): fall back for incompatible local regex evaluation - #277

Merged
marandaneto merged 1 commit into
mainfrom
fix/local-regex-compatibility-5354
Oct 23, 2026
Merged

marandaneto merged 1 commit into
mainfrom
fix/local-regex-compatibility-5354

Conversation

@marandaneto

Copy link
Copy Markdown
Member

💡 Motivation and Context

Local feature flag regex evaluation can return a different Boolean than the flags service and skip remote fallback. For example, PCRE matches ^foo$ against "foo\n", but the service does not. PCRE also rejects (?<=a+)b even though the service accepts it. Unicode word classes differ too: ^\w$ does not match U+200C locally, but does on the service.

This adapts the agreed shared-syntax/local-evaluation approach from posthog-js#5354 and its cross-SDK discussion to PHP's PCRE engine. It does not assume that the JS PR is merged or released.

Changes

  • Keep Unicode mode and use strict default end anchors with /uD.
  • Defer unsupported or non-equivalent groups, escapes, classes and repetitions through the existing inconclusive exception and remote fallback.
  • For actual \w or \W escapes, keep ASCII, Unicode letters and decimal digits local. Conservatively defer other input categories, including join controls, marks, non-decimal numerals and symbols. Escaped literal word markers and Unicode patterns without these shorthands are unaffected.
  • Keep invalid patterns within the admitted subset false for both operators. Preserve empty-pattern matching and existing stringification. Execution errors remain inconclusive, including during the empty-input preflight, rather than becoming matches through not_regex or cohort negation.
  • Cover direct, cohort and negated-leaf fallback, plus unresolved local-only results. Include a patch change intent.

This deliberately reduces local coverage for advanced syntax and uncertain word-class inputs, even where native matching sometimes agrees. It adds no engine, dependency or public option and makes no universal regex, Unicode or resource-budget equivalence claim.

💚 How did you test it?

  • Added tests before the fixes and recorded failing assertions for the original mismatch, preflight execution failure and Unicode word-class revision.
  • After integrating current main, focused PHPUnit passed: 701 tests and 1,794 assertions. Full PHPUnit passed: 2,131 tests and 8,923 assertions.
  • Compared the actual SDK matcher against frozen actual-service results: 10,112 comparisons, zero wrong definitive results. These finite corpora overlap and do not represent exhaustive coverage.
  • Verified public fallback and local-only behavior with the existing mocked HTTP fixture, without mocking the regex engine or calling live services.
  • composer api:check and strict Composer validation passed. Scoped PHPCS has zero errors; existing warnings and suite deprecations remain.

Local validation used PHP 8.5.10 / PCRE 10.48. A prior unchanged loopback timeout test flaked under concurrent activity, then passed targeted and sequential full retries without changes. The fresh post-main full run also passed. No coverage driver or additional PHP-version matrix was available locally. The literal whole-directory lint command previously exhausted memory while scanning installed vendor files, so repository source/tests were linted directly. CI results are not yet claimed.

📝 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: Human-driven (agent-assisted)

@marandaneto directed the compatibility fix and publication. Pi agents used local file and shell tools, PHP/Composer/PHPUnit, Git, GitHub CLI and the isolated autoreview helper. No public agent-session link is available. Human review is required.

The implementation keeps proven shared behavior local and uses existing fallback for uncertain PCRE/service differences. Test-audit and behavior-neutral deslop checks were completed. Final isolated autoreview passed on the committed branch against origin/main, with no actionable findings. No dependency replacement or public API expansion was used.

@marandaneto marandaneto self-assigned this Oct 22, 2026
@github-actions

Copy link
Copy Markdown
Contributor

posthog-php-fork_curl Compliance Report

Date: 2026-10-22T18:36:50.104513+00:00
Duration: 112398ms

⚠️ 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 ✅ 34ms
Format Validation.Event Has Uuid ✅ 529ms
Format Validation.Event Has Lib Properties ✅ 531ms
Format Validation.Distinct Id Is String ✅ 529ms
Format Validation.Token Is Present ✅ 530ms
Format Validation.Custom Properties Preserved ✅ 530ms
Format Validation.Event Has Timestamp ✅ 531ms
Format Validation.Non Utc Event Timestamp Is Converted To Utc ✅ 530ms
Retry Behavior.Retries On 503 ❌ 5533ms
Retry Behavior.Does Not Retry On 400 ✅ 2534ms
Retry Behavior.Does Not Retry On 401 ✅ 2532ms
Retry Behavior.Respects Retry After Header ❌ 5535ms
Retry Behavior.Implements Backoff ❌ 15537ms
Retry Behavior.Retries On 500 ❌ 5044ms
Retry Behavior.Retries On 502 ❌ 5536ms
Retry Behavior.Retries On 504 ❌ 5535ms
Retry Behavior.Max Retries Respected ❌ 15545ms
Deduplication.Generates Unique Uuids ✅ 539ms
Deduplication.Preserves Uuid On Retry ❌ 5532ms
Deduplication.Preserves Uuid And Timestamp On Retry ❌ 10543ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry ❌ 5540ms
Deduplication.No Duplicate Events In Batch ✅ 537ms
Deduplication.Different Events Have Different Uuids ✅ 532ms
Compression.Sends Gzip When Enabled ✅ 533ms
Batch Format.Uses Proper Batch Structure ✅ 531ms
Batch Format.Flush With No Events Sends Nothing ✅ 519ms
Batch Format.Multiple Events Batched Together ✅ 520ms
Error Handling.Does Not Retry On 403 ✅ 2532ms
Error Handling.Does Not Retry On 413 ✅ 2533ms
Error Handling.Retries On 408 ❌ 5536ms

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 ✅ 522ms
Request Payload.Flags Request Hits Flags Path Not Decide ✅ 522ms
Request Payload.Flags Request Omits Authorization Header ✅ 521ms
Request Payload.Token In Flags Body Matches Init ✅ 521ms
Request Payload.Groups Round Trip ✅ 522ms
Request Payload.Groups Default To Empty Object ✅ 522ms
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 ✅ 521ms
Request Lifecycle.No Flags Request On Init Alone ✅ 517ms
Request Lifecycle.No Flags Request On Normal Capture ✅ 516ms
Request Lifecycle.Two Flag Calls Produce Two Remote Requests ✅ 525ms
Request Lifecycle.Mock Response Value Is Returned To Caller ✅ 523ms
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 ✅ 537ms

@github-actions

Copy link
Copy Markdown
Contributor

posthog-php-socket Compliance Report

Date: 2026-10-22T18:37:10.061617+00:00
Duration: 114783ms

⚠️ Some Tests Failed

45/47 tests passed, 2 failed


Capture Tests

⚠️ 28/30 tests passed, 2 failed

View Details
Test Status Duration
Format Validation.Event Has Required Fields ✅ 27ms
Format Validation.Event Has Uuid ✅ 522ms
Format Validation.Event Has Lib Properties ✅ 521ms
Format Validation.Distinct Id Is String ✅ 523ms
Format Validation.Token Is Present ✅ 522ms
Format Validation.Custom Properties Preserved ✅ 522ms
Format Validation.Event Has Timestamp ✅ 522ms
Format Validation.Non Utc Event Timestamp Is Converted To Utc ✅ 522ms
Retry Behavior.Retries On 503 ✅ 5829ms
Retry Behavior.Does Not Retry On 400 ✅ 2524ms
Retry Behavior.Does Not Retry On 401 ✅ 2527ms
Retry Behavior.Respects Retry After Header ❌ 5630ms
Retry Behavior.Implements Backoff ✅ 16240ms
Retry Behavior.Retries On 500 ✅ 5134ms
Retry Behavior.Retries On 502 ✅ 5630ms
Retry Behavior.Retries On 504 ✅ 5630ms
Retry Behavior.Max Retries Respected ✅ 16244ms
Deduplication.Generates Unique Uuids ✅ 530ms
Deduplication.Preserves Uuid On Retry ✅ 5629ms
Deduplication.Preserves Uuid And Timestamp On Retry ✅ 10837ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry ✅ 5634ms
Deduplication.No Duplicate Events In Batch ✅ 529ms
Deduplication.Different Events Have Different Uuids ✅ 523ms
Compression.Sends Gzip When Enabled ✅ 522ms
Batch Format.Uses Proper Batch Structure ✅ 522ms
Batch Format.Flush With No Events Sends Nothing ✅ 518ms
Batch Format.Multiple Events Batched Together ✅ 512ms
Error Handling.Does Not Retry On 403 ✅ 2524ms
Error Handling.Does Not Retry On 413 ✅ 2523ms
Error Handling.Retries On 408 ❌ 5531ms

Failures

retry_behavior.respects_retry_after_header

Retry delay too short: 101ms < 2500ms

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 ✅ 523ms
Request Payload.Flags Request Uses V2 Query Param ✅ 521ms
Request Payload.Flags Request Hits Flags Path Not Decide ✅ 522ms
Request Payload.Flags Request Omits Authorization Header ✅ 521ms
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 ✅ 523ms
Request Payload.Disable Geoip Omitted Defaults To False ✅ 521ms
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 ✅ 508ms
Request Lifecycle.Two Flag Calls Produce Two Remote Requests ✅ 526ms
Request Lifecycle.Mock Response Value Is Returned To Caller ✅ 524ms
Retry Behavior.Retries Flags On 502 ✅ 624ms
Retry Behavior.Retries Flags On 504 ✅ 624ms
Side Effect Events.Get Feature Flag Captures Feature Flag Called Event ✅ 524ms

@github-actions

Copy link
Copy Markdown
Contributor

posthog-php-lib_curl Compliance Report

Date: 2026-10-22T18:37:13.224379+00:00
Duration: 116402ms

⚠️ Some Tests Failed

46/47 tests passed, 1 failed


Capture Tests

⚠️ 29/30 tests passed, 1 failed

View Details
Test Status Duration
Format Validation.Event Has Required Fields ✅ 24ms
Format Validation.Event Has Uuid ✅ 520ms
Format Validation.Event Has Lib Properties ✅ 521ms
Format Validation.Distinct Id Is String ✅ 521ms
Format Validation.Token Is Present ✅ 521ms
Format Validation.Custom Properties Preserved ✅ 521ms
Format Validation.Event Has Timestamp ✅ 521ms
Format Validation.Non Utc Event Timestamp Is Converted To Utc ✅ 521ms
Retry Behavior.Retries On 503 ✅ 5830ms
Retry Behavior.Does Not Retry On 400 ✅ 2523ms
Retry Behavior.Does Not Retry On 401 ✅ 2523ms
Retry Behavior.Respects Retry After Header ❌ 6330ms
Retry Behavior.Implements Backoff ✅ 16243ms
Retry Behavior.Retries On 500 ✅ 5630ms
Retry Behavior.Retries On 502 ✅ 5629ms
Retry Behavior.Retries On 504 ✅ 5629ms
Retry Behavior.Max Retries Respected ✅ 17043ms
Deduplication.Generates Unique Uuids ✅ 227ms
Deduplication.Preserves Uuid On Retry ✅ 5629ms
Deduplication.Preserves Uuid And Timestamp On Retry ✅ 10835ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry ✅ 5632ms
Deduplication.No Duplicate Events In Batch ✅ 525ms
Deduplication.Different Events Have Different Uuids ✅ 522ms
Compression.Sends Gzip When Enabled ✅ 521ms
Batch Format.Uses Proper Batch Structure ✅ 521ms
Batch Format.Flush With No Events Sends Nothing ✅ 517ms
Batch Format.Multiple Events Batched Together ✅ 511ms
Error Handling.Does Not Retry On 403 ✅ 2523ms
Error Handling.Does Not Retry On 413 ✅ 2523ms
Error Handling.Retries On 408 ✅ 5628ms

Failures

retry_behavior.respects_retry_after_header

Retry delay too short: 803ms < 2500ms

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 ✅ 519ms
Request Payload.Flags Request Hits Flags Path Not Decide ✅ 520ms
Request Payload.Flags Request Omits Authorization Header ✅ 519ms
Request Payload.Token In Flags Body Matches Init ✅ 519ms
Request Payload.Groups Round Trip ✅ 519ms
Request Payload.Groups Default To Empty Object ✅ 520ms
Request Payload.Disable Geoip False Propagates As Geoip Disable False ✅ 519ms
Request Payload.Disable Geoip Omitted Defaults To False ✅ 519ms
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 ✅ 507ms
Request Lifecycle.Two Flag Calls Produce Two Remote Requests ✅ 522ms
Request Lifecycle.Mock Response Value Is Returned To Caller ✅ 521ms
Retry Behavior.Retries Flags On 502 ✅ 622ms
Retry Behavior.Retries Flags On 504 ✅ 621ms
Side Effect Events.Get Feature Flag Captures Feature Flag Called Event ✅ 521ms

@posthog posthog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PostHog Review · Standard · reviewed aee01e2: no findings.

This Standard review started from the posthog-review label. Comment @posthog review --deep for a Deep review, or @posthog help to see all commands.

@marandaneto
marandaneto marked this pull request as ready for review October 23, 2026 08:43
@marandaneto
marandaneto requested a review from a team as a code owner October 23, 2026 08:43
@posthog posthog Bot removed the reviewhog label Oct 23, 2026
@marandaneto
marandaneto merged commit 547e062 into main Oct 23, 2026
29 checks passed
@marandaneto
marandaneto deleted the fix/local-regex-compatibility-5354 branch October 23, 2026 10:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants