feat: Support device_id as bucketing identifier for local evaluation#424
feat: Support device_id as bucketing identifier for local evaluation#424
Conversation
83cbc1f to
03d2ff8
Compare
posthog-python Compliance ReportDate: 2026-02-13 15:19:11 UTC ✅ All Tests Passed!29/29 tests passed Capture Tests✅ 29/29 tests passed View Details
|
dmarticus
left a comment
There was a problem hiding this comment.
A few questions but nothing blocking.
Also, re: your open question about design, I agree with option (a). Silently falling back to distinct_id would mask configuration bugs and defeat the purpose of device_id bucketing. The current behavior of raising InconclusiveMatchError → server fallback is the right call. It's observable (you can log/monitor fallback rates) and self-correcting (server has the device_id from the request context or can handle it differently).
posthog/client.py
Outdated
| focused_group_properties, | ||
| self.feature_flags_by_key, | ||
| evaluation_cache, | ||
| bucketing_value=groups[group_name], |
There was a problem hiding this comment.
I don't think breaks anything, but it feels a little confusing technically – this is passed positionally to match_feature_flag_properties, but bucketing_value is a keyword-only arg (or at least should be treated as one given the * separator in is_condition_match). More importantly, you're adding bucketing_value as a kwarg to match_feature_flag_properties but passing it
positionally here; I'd double check this actually lands in the right parameter. Looking at the signature, it seems fine since it's a named kwarg, but worth verifying.
There was a problem hiding this comment.
oh and also, If group_name isn't in groups, this will raise a KeyError. The existing code presumably handles this elsewhere, but worth confirming since you're now accessing it in a new location (before, group_name was only used in the properties lookup path).
There was a problem hiding this comment.
good call out! i went a few extra rounds to refactor this a bit and made the code more defensive
Additional Comments (1)
This changes behavior for callers that provide Prompt To Fix With AIThis is a comment left during a code review.
Path: posthog/client.py
Line: 1452:1468
Comment:
**Group properties default changed**
In `_compute_flag_locally` the group properties passed to `match_feature_flag_properties` changed from `group_properties[group_name]` to `group_properties.get(group_name, {})`. Previously, if `groups[group_name]` was present but `group_properties` lacked that key, this would `KeyError` and trigger API fallback; now it silently evaluates with `{}` and can return a definitive `False/variant` locally.
This changes behavior for callers that provide `groups` but forget corresponding `group_properties`, and may prevent intended server fallback. If the previous fallback was relied upon, restore the `KeyError` behavior (or explicitly raise `InconclusiveMatchError`) when `group_properties` is missing for the group flag.
How can I resolve this? If you propose a fix, please make it concise. |
efeaf6f to
20b5732
Compare
Add support for `bucketing_identifier` field on feature flags to allow using `device_id` instead of `distinct_id` for hashing/bucketing in local evaluation. - When `bucketing_identifier: "device_id"`, use device_id for hash calculations instead of distinct_id - device_id can be passed as method parameter or resolved from context via `get_context_device_id()` - If device_id is required but not provided, raises InconclusiveMatchError to trigger server fallback - Group flags ignore bucketing_identifier and always use group identifier
…aluation - Rename _hash param from distinct_id to identifier since it now receives device IDs, group keys, and distinct IDs - Replace skip_bucketing_identifier boolean with explicit hashing_identifier param so callers pass the resolved identifier directly for group flags - Make hashing_identifier a required keyword arg in is_condition_match, removing a dead fallback that silently masked potential bugs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The group path in _compute_flag_locally was passing positional args in the wrong order to match_feature_flag_properties: feature_flags_by_key landed in cohort_properties and evaluation_cache landed in flags_by_key. Switch to keyword args to make the mapping explicit and correct.
Update call sites to use keyword arguments after the positional-to-keyword enforcement change in match_feature_flag_properties.
Extract bucketing value resolution into a single resolve_bucketing_value() helper, called in _compute_flag_locally and evaluate_flag_dependency, instead of resolving it inside match_feature_flag_properties. bucketing_value is now a required keyword argument on match_feature_flag_properties.
20b5732 to
3b465b0
Compare
Problem
Feature flags need to support using
device_idinstead ofdistinct_idfor bucketing/hashing in local evaluation. This allows consistent flag experiences across anonymous and identified users on the same device.Changes
bucketing_identifierfield support onflag["filters"]- can be"distinct_id","device_id", or null/missing (defaults to"distinct_id")bucketing_identifier: "device_id", use device_id for hash calculations instead of distinct_idget_context_device_id()InconclusiveMatchErrorto trigger server fallbackbucketing_identifierand always use group identifier (viaskip_bucketing_identifierparameter)Question to the feature flags team: If a flag is configured to use
device_idhas bucketing identifier, but none is provided, should we:a) raise an error and return "false" (current behavior)
b) fall back to use
distinct_idand return whatever that resolves toPersonally I prefer a), as I would like to know when this happens and fix my code.
Testing
bucketing_identifier: "device_id"uses device_id for hashingbucketing_identifieruses distinct_idget_context_device_id()is used when param not providedaggregation_group_type_indexcontinues to use group identifieronly_evaluate_locally=Truereturns None when device_id required but missingget_all_flagsproperly handles device_id bucketing