You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
While shipping starts_with/ends_with local evaluation across the SDKs (#72992), a pattern emerged: our case-insensitive property matching is inconsistent between the SDKs and the flags service, and within the flags service itself. Filing this so we can settle it once instead of per code review.
How the flags service folds case
In rust/feature-flags/src/properties/property_matching.rs:
icontains / starts_with / ends_with (and negations): to_ascii_lowercase(), ASCII-only. The code comment says it's a deliberate perf choice.
exact / is_not: full Unicode to_lowercase().
So the server already applies two different folding rules depending on operator.
What the SDKs use for the icontains/starts_with/ends_with family
Concretely: person property "Äbc" with starts_with: "ä" matches locally in Python/Go/Node/Rust/.NET and does not match on /flags. Same flag, same person, different answer depending on evaluation path — exactly the divergence local evaluation is supposed to avoid.
Adjacent inconsistencies in the same code
The Rust SDK's exact operator uses eq_ignore_ascii_case while the server's exact is Unicode — the mirror image of the divergence above.
Numeric stringification: the server renders a float-typed 323.0 as "323.0" (serde Display), as do Ruby and Python, but Go and Node render "323", so a JSON 323.0 property matches ends_with: "3" locally but not remotely. .NET additionally used the host locale for decimal separators (3.14 → "3,14" on de-DE), fixed in fix: use invariant culture when stringifying property values for matching posthog-dotnet#270.
Options
Standardize the SDKs on ASCII-only folding for the icontains/starts/ends family to match the server. PHP and Ruby are already there; the rest are one-line changes plus tests. The numeric stringification fixes for Go/Node belong in the same sweep.
Make the server Unicode-aware and leave the SDKs alone. Friendlier for international text, but changes existing server behavior, and the ASCII choice was deliberate.
Leave it and document the edge case.
I lean toward 1, plus an explicit decision on whether the server's Unicode exact/is_not is intentional. Non-ASCII values differing only by case are rare, but today the answer depends on which SDK and which operator you hit, and that's the worst version.
While shipping
starts_with/ends_withlocal evaluation across the SDKs (#72992), a pattern emerged: our case-insensitive property matching is inconsistent between the SDKs and the flags service, and within the flags service itself. Filing this so we can settle it once instead of per code review.How the flags service folds case
In
rust/feature-flags/src/properties/property_matching.rs:icontains/starts_with/ends_with(and negations):to_ascii_lowercase(), ASCII-only. The code comment says it's a deliberate perf choice.exact/is_not: full Unicodeto_lowercase().So the server already applies two different folding rules depending on operator.
What the SDKs use for the icontains/starts_with/ends_with family
strtolower(ASCII-only)downcase(:ascii)as of PostHog/posthog-ruby#229casefold()(most aggressive Unicode;"ss"matches"ß")strings.ToLower(Unicode)toLowerCase()(Unicode)to_lowercase()(Unicode)OrdinalIgnoreCase(Unicode simple folding)Concretely: person property
"Äbc"withstarts_with: "ä"matches locally in Python/Go/Node/Rust/.NET and does not match on/flags. Same flag, same person, different answer depending on evaluation path — exactly the divergence local evaluation is supposed to avoid.Adjacent inconsistencies in the same code
exactoperator useseq_ignore_ascii_casewhile the server'sexactis Unicode — the mirror image of the divergence above.323.0as"323.0"(serdeDisplay), as do Ruby and Python, but Go and Node render"323", so a JSON323.0property matchesends_with: "3"locally but not remotely. .NET additionally used the host locale for decimal separators (3.14→"3,14"on de-DE), fixed in fix: use invariant culture when stringifying property values for matching posthog-dotnet#270.Options
I lean toward 1, plus an explicit decision on whether the server's Unicode
exact/is_notis intentional. Non-ASCII values differing only by case are rare, but today the answer depends on which SDK and which operator you hit, and that's the worst version.