Skip to content

Commit cfafe66

Browse files
Fix holdout priority order - global holdouts take precedence
CRITICAL FIX: When a user is bucketed into both global and local holdouts, global holdouts must take precedence. Python SDK was evaluating local holdouts first, causing wrong holdout to be returned. Root Cause: - In project_config.py lines 237-245, holdouts were added in wrong order: 1. Local (included) holdouts first 2. Global holdouts second - When get_decision_for_flag iterates through holdouts, it returns the first one that buckets the user - This meant local holdouts had higher priority than global holdouts Expected Behavior (from decide_holdouts.feature:167): - User ppid8807 hits BOTH ho_local_40p (local) and ho_global_15p (global) - Global holdout should take precedence - Should return ho_global_15p, not ho_local_40p The Fix: - Reversed order in flag_holdouts_map construction - Global holdouts added FIRST (lines 237-242) - Local holdouts added SECOND (lines 244-246) - Now global holdouts are evaluated before local holdouts Code Changes (project_config.py lines 237-246): ```python # Add global holdouts FIRST (they have higher priority) excluded_holdouts = self.excluded_holdouts.get(flag_id, []) for holdout in self.global_holdouts: if holdout not in excluded_holdouts: applicable_holdouts.append(holdout) # Add flag-specific included holdouts AFTER global holdouts if flag_id in self.included_holdouts: applicable_holdouts.extend(self.included_holdouts[flag_id]) ``` Impact: - Global holdouts now correctly take precedence over local holdouts - Fixes decide_holdouts.feature scenario 3 (ppid8807 priority test) - All 47 tests passing (12 config + 35 decision_service_holdout) - Matches Swift SDK holdout priority behavior Aligned with Swift SDK holdout evaluation order.
1 parent c5fbb62 commit cfafe66

File tree

1 file changed

+6
-5
lines changed

1 file changed

+6
-5
lines changed

optimizely/project_config.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -234,16 +234,17 @@ def __init__(self, datafile: str | bytes, logger: Logger, error_handler: Any):
234234
flag_id = feature.id
235235
applicable_holdouts: list[entities.Holdout] = []
236236

237-
# Add flag-specific included holdouts first
238-
if flag_id in self.included_holdouts:
239-
applicable_holdouts.extend(self.included_holdouts[flag_id])
240-
241-
# Add global holdouts (excluding any that explicitly exclude this flag)
237+
# Add global holdouts FIRST (they have higher priority)
238+
# Excluding any that explicitly exclude this flag
242239
excluded_holdouts = self.excluded_holdouts.get(flag_id, [])
243240
for holdout in self.global_holdouts:
244241
if holdout not in excluded_holdouts:
245242
applicable_holdouts.append(holdout)
246243

244+
# Add flag-specific included holdouts AFTER global holdouts
245+
if flag_id in self.included_holdouts:
246+
applicable_holdouts.extend(self.included_holdouts[flag_id])
247+
247248
if applicable_holdouts:
248249
self.flag_holdouts_map[feature.key] = applicable_holdouts
249250

0 commit comments

Comments
 (0)