Skip to content

feat: Support device_id as bucketing identifier for local evaluation#424

Open
andehen wants to merge 2 commits intomasterfrom
flags/support-device-id-in-local-eval
Open

feat: Support device_id as bucketing identifier for local evaluation#424
andehen wants to merge 2 commits intomasterfrom
flags/support-device-id-in-local-eval

Conversation

@andehen
Copy link
Contributor

@andehen andehen commented Feb 5, 2026

Problem

Feature flags need to support using device_id instead of distinct_id for bucketing/hashing in local evaluation. This allows consistent flag experiences across anonymous and identified users on the same device.

Changes

  • Add bucketing_identifier field support on flag["filters"] - can be "distinct_id", "device_id", or null/missing (defaults to "distinct_id")
  • When bucketing_identifier: "device_id", use device_id for hash calculations instead of distinct_id
  • device_id can be passed as a method parameter or resolved from context via get_context_device_id()
  • If device_id is required but not provided, raise InconclusiveMatchError to trigger server fallback
  • Group flags ignore bucketing_identifier and always use group identifier (via skip_bucketing_identifier parameter)

Question to the feature flags team: If a flag is configured to use device_id has bucketing identifier, but none is provided, should we:
a) raise an error and return "false" (current behavior)
b) fall back to use distinct_id and return whatever that resolves to

Personally I prefer a), as I would like to know when this happens and fix my code.

Testing

  • Basic device_id bucketing - flag with bucketing_identifier: "device_id" uses device_id for hashing
  • Same device_id with different distinct_ids produces same result
  • Missing device_id fallback - raises InconclusiveMatchError, triggers server evaluation
  • Default to distinct_id - null/missing bucketing_identifier uses distinct_id
  • Multivariate with device_id - variant selection uses device_id hash
  • device_id from context - get_context_device_id() is used when param not provided
  • Group flags unaffected - aggregation_group_type_index continues to use group identifier
  • only_evaluate_locally=True returns None when device_id required but missing
  • get_all_flags properly handles device_id bucketing
  • All existing tests pass (631 passed)

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
@andehen andehen force-pushed the flags/support-device-id-in-local-eval branch from 83cbc1f to 03d2ff8 Compare February 5, 2026 12:40
@github-actions
Copy link
Contributor

github-actions bot commented Feb 5, 2026

posthog-python Compliance Report

Date: 2026-02-06 09:56:38 UTC
Duration: 145103ms

⚠️ Some Tests Failed

27/29 tests passed, 2 failed


Capture Tests

⚠️ 27/29 tests passed, 2 failed

View Details
Test Status Duration
Format Validation.Event Has Required Fields 516ms
Format Validation.Event Has Uuid 1507ms
Format Validation.Event Has Lib Properties 1507ms
Format Validation.Distinct Id Is String 1507ms
Format Validation.Token Is Present 1507ms
Format Validation.Custom Properties Preserved 1507ms
Format Validation.Event Has Timestamp 1506ms
Retry Behavior.Retries On 503 7924ms
Retry Behavior.Does Not Retry On 400 3506ms
Retry Behavior.Does Not Retry On 401 3508ms
Retry Behavior.Respects Retry After Header 7312ms
Retry Behavior.Implements Backoff 20618ms
Retry Behavior.Retries On 500 6687ms
Retry Behavior.Retries On 502 7172ms
Retry Behavior.Retries On 504 6811ms
Retry Behavior.Max Retries Respected 21019ms
Deduplication.Generates Unique Uuids 1496ms
Deduplication.Preserves Uuid On Retry 6529ms
Deduplication.Preserves Uuid And Timestamp On Retry 14044ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry 6779ms
Deduplication.No Duplicate Events In Batch 1503ms
Deduplication.Different Events Have Different Uuids 1507ms
Compression.Sends Gzip When Enabled 1507ms
Batch Format.Uses Proper Batch Structure 1507ms
Batch Format.Flush With No Events Sends Nothing 1005ms
Batch Format.Multiple Events Batched Together 1505ms
Error Handling.Does Not Retry On 403 3509ms
Error Handling.Does Not Retry On 413 3507ms
Error Handling.Retries On 408 6509ms

Failures

retry_behavior.respects_retry_after_header

Retry delay too short: 803ms < 2500ms

error_handling.retries_on_408

Expected at least 2 requests, got 1

…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>
@andehen andehen requested review from a team February 6, 2026 11:37
@posthog-project-board-bot posthog-project-board-bot bot moved this to In Review in Feature Flags Feb 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In Review

Development

Successfully merging this pull request may close these issues.

1 participant