From df282a6e4d01c0ca37d418d6c48cbc51db9c91b4 Mon Sep 17 00:00:00 2001 From: Luan Taraschi <130802253+luantaraschi@users.noreply.github.com> Date: Fri, 30 Oct 2026 11:29:22 -0300 Subject: [PATCH] fix(flags): stringify regex properties as compact JSON Local regex and not_regex matching called str() on the property. --- .../local-eval-regex-json-string.md | 5 +++++ posthog/feature_flags.py | 4 +++- posthog/test/test_feature_flags.py | 19 +++++++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 .sampo/changesets/local-eval-regex-json-string.md diff --git a/.sampo/changesets/local-eval-regex-json-string.md b/.sampo/changesets/local-eval-regex-json-string.md new file mode 100644 index 00000000..d7f44458 --- /dev/null +++ b/.sampo/changesets/local-eval-regex-json-string.md @@ -0,0 +1,5 @@ +--- +posthog: patch +--- + +Stringify non-string properties as compact JSON for local `regex` and `not_regex` matching, so booleans, lists, and objects match the same way they do on the flags service. diff --git a/posthog/feature_flags.py b/posthog/feature_flags.py index e0ffda65..cfe847fb 100644 --- a/posthog/feature_flags.py +++ b/posthog/feature_flags.py @@ -1264,7 +1264,9 @@ def compute_exact_match(value, override_value): return matched if operator in ("regex", "not_regex"): - property_string = "null" if override_value is None else str(override_value) + property_string = _value_to_string( + override_value, UnresolvedFlagReason.MISSING_CONTEXT + ) regex_match = _match_regex(str(value), property_string) if regex_match is None: return False diff --git a/posthog/test/test_feature_flags.py b/posthog/test/test_feature_flags.py index 14a1ed18..9c79813e 100644 --- a/posthog/test/test_feature_flags.py +++ b/posthog/test/test_feature_flags.py @@ -5871,6 +5871,25 @@ def test_match_properties_regex(self): self.assertFalse(match_property(property_d, {"key": "value"})) + @parameterized.expand( + [ + ("list", r"\[1,2\]", [1, 2], True), + ("bool", "^true$", True, True), + ("python_bool_spelling", "True", True, False), + ("object", r'^\{"a":1,"b":\[null\]\}$', {"b": [None], "a": 1}, True), + ("python_object_spelling", "'a'", {"a": 1}, False), + ] + ) + def test_regex_stringifies_non_string_properties_as_compact_json( + self, _name, pattern, property_value, expected + ): + regex = self.property("key", pattern, "regex") + not_regex = self.property("key", pattern, "not_regex") + self.assertEqual(expected, match_property(regex, {"key": property_value})) + self.assertEqual( + not expected, match_property(not_regex, {"key": property_value}) + ) + def test_match_properties_math_operators(self): property_a = self.property(key="key", value=1, operator="gt") self.assertTrue(match_property(property_a, {"key": 2}))