Skip to content

feat(posthog-server): support starts_with/ends_with local flag operators - #691

Open
dustinbyrne wants to merge 2 commits into
mainfrom
posthog-code/local-eval-string-prefix-suffix-operators
Open

feat(posthog-server): support starts_with/ends_with local flag operators#691
dustinbyrne wants to merge 2 commits into
mainfrom
posthog-code/local-eval-string-prefix-suffix-operators

Conversation

@dustinbyrne

@dustinbyrne dustinbyrne commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

💡 Motivation and Context

The local-feature-flag-evaluator cross-SDK contract now requires the local evaluator to support the starts_with, not_starts_with, ends_with, and not_ends_with property-filter operators (see PostHog/posthog#72992, already shipped in posthog-js/node, python, php, ruby, go, and dotnet).

posthog-server was missing these: the PropertyOperator enum had no members for them, so any flag targeting on one of these operators fell through the matching switch to InconclusiveMatchException("Unknown operator: ...") and could never be evaluated locally — it always fell back to remote evaluation.

Why: bring posthog-server's local flag evaluation into parity with the shared SDK contract so these operators resolve locally instead of forcing a remote round-trip.

This is purely additive and backward-compatible — it only makes previously-inconclusive evaluations resolve locally. No deprecation or major version needed.

Changes

  • Add STARTS_WITH, NOT_STARTS_WITH, ENDS_WITH, NOT_ENDS_WITH to the PropertyOperator enum (plus fromString and Gson serialization mappings).
  • Implement matching in FlagEvaluator, mirroring the existing ICONTAINS case: stringify both the property value and filter value, ASCII case-fold both, then prefix/suffix compare, negating for the not_* variants. A missing property is treated as inconclusive, consistent with the other operators.
  • Regenerated posthog/api/posthog.api for the new public enum members.
  • Added a changeset (posthog + posthog-server minor).

💚 How did you test it?

Added unit tests in FlagEvaluatorTest covering each new operator (matching and non-matching cases), ASCII-only case-folding at non-ASCII boundaries, and a missing-property → inconclusive case. Added wire-format parsing and serializer round-trip coverage for all four operators. Ran ./gradlew :posthog:test :posthog-server:test and make checkFormat.

📝 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 changeset to generate a changeset file

🤖 Agent context

Autonomy: Fully autonomous

Authored by PostHog Code (Claude Opus 4.8). Task: implement the "String prefix/suffix property filter operators" requirement added to the local-feature-flag-evaluator contract for the JVM server SDK. Implementation follows the shared contract's ASCII-only case-folding requirement for prefix/suffix operators, while leaving the existing ICONTAINS normalization unchanged. Verified via new unit tests, the full posthog-server test suite, and spotless.


Created with PostHog Code

Add STARTS_WITH, NOT_STARTS_WITH, ENDS_WITH, and NOT_ENDS_WITH to the
PropertyOperator enum and implement matching in the local FlagEvaluator,
mirroring the existing ICONTAINS case (stringify + case-fold both sides,
prefix/suffix compare, negate for not_* variants). A missing property is
treated as inconclusive, consistent with the other operators.

Previously flags targeting on these operators could never be evaluated
locally and always fell back to remote evaluation.

Implements the "String prefix/suffix property filter operators"
requirement from the local-feature-flag-evaluator contract.

Generated-By: PostHog Code
Task-Id: db3b3000-a83b-4ec8-b2e8-e51948d6cd92
@github-actions

github-actions Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

posthog-android Compliance Report

Date: 2026-08-11 16:15:36 UTC
Duration: 118336ms

✅ All Tests Passed!

46/46 tests passed


Capture Tests

29/29 tests passed

View Details
Test Status Duration
Format Validation.Event Has Required Fields 342ms
Format Validation.Event Has Uuid 28ms
Format Validation.Event Has Lib Properties 27ms
Format Validation.Distinct Id Is String 25ms
Format Validation.Token Is Present 27ms
Format Validation.Custom Properties Preserved 30ms
Format Validation.Event Has Timestamp 27ms
Retry Behavior.Retries On 503 7026ms
Retry Behavior.Does Not Retry On 400 4024ms
Retry Behavior.Does Not Retry On 401 4027ms
Retry Behavior.Respects Retry After Header 7025ms
Retry Behavior.Implements Backoff 17032ms
Retry Behavior.Retries On 500 7019ms
Retry Behavior.Retries On 502 7016ms
Retry Behavior.Retries On 504 7019ms
Retry Behavior.Max Retries Respected 17037ms
Deduplication.Generates Unique Uuids 38ms
Deduplication.Preserves Uuid On Retry 7014ms
Deduplication.Preserves Uuid And Timestamp On Retry 12029ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry 7017ms
Deduplication.No Duplicate Events In Batch 35ms
Deduplication.Different Events Have Different Uuids 21ms
Compression.Sends Gzip When Enabled 20ms
Batch Format.Uses Proper Batch Structure 18ms
Batch Format.Flush With No Events Sends Nothing 13ms
Batch Format.Multiple Events Batched Together 30ms
Error Handling.Does Not Retry On 403 4022ms
Error Handling.Does Not Retry On 413 4021ms
Error Handling.Retries On 408 5026ms

Feature_Flags Tests

17/17 tests passed

View Details
Test Status Duration
Request Payload.Request With Person Properties Device Id 33ms
Request Payload.Flags Request Uses V2 Query Param 22ms
Request Payload.Flags Request Hits Flags Path Not Decide 20ms
Request Payload.Flags Request Omits Authorization Header 26ms
Request Payload.Token In Flags Body Matches Init 19ms
Request Payload.Groups Round Trip 20ms
Request Payload.Groups Default To Empty Object 20ms
Request Payload.Disable Geoip False Propagates As Geoip Disable False 29ms
Request Payload.Disable Geoip Omitted Defaults To False 25ms
Request Payload.Flag Keys To Evaluate Contains Only Requested Key 21ms
Request Lifecycle.No Flags Request On Init Alone 10ms
Request Lifecycle.No Flags Request On Normal Capture 23ms
Request Lifecycle.Two Flag Calls Produce Two Remote Requests 36ms
Request Lifecycle.Mock Response Value Is Returned To Caller 18ms
Retry Behavior.Retries Flags On 502 323ms
Retry Behavior.Retries Flags On 504 322ms
Side Effect Events.Get Feature Flag Captures Feature Flag Called Event 23ms

@posthog

posthog Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

🦔 ReviewHog reviewed this pull request

Found 0 must fix, 0 should fix, 4 consider.

Published 4 findings (view the review).

@posthog

posthog Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

ReviewHog Alpha 🦔 If you find any issues helpful - please reply "valid", "invalid", etc., for evaluation purposes 🙏

@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.

ReviewHog Report

Changes

Issues: 4 issues

Files (5)
  • .changeset/string-prefix-suffix-operators.md
  • posthog-server/src/main/java/com/posthog/server/internal/FlagEvaluator.kt
  • posthog/api/posthog.api
  • posthog/src/main/java/com/posthog/internal/GsonPropertyOperatorAdapter.kt
  • posthog/src/main/java/com/posthog/internal/PostHogLocalEvaluationModels.kt

@dustinbyrne
dustinbyrne marked this pull request as ready for review August 11, 2026 16:54
@dustinbyrne
dustinbyrne requested a review from a team as a code owner August 11, 2026 16:54
@greptile-apps

greptile-apps Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Reviews (1): Last reviewed commit: "fix(posthog-server): align prefix suffix..." | Re-trigger Greptile

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.

1 participant