·
Last changedVersion:
This version passed the hardening gate. Hardening applied 4 targeted changes and preserved all other text. Classified findings before hardening: 0. Classified findings closed: 0. Classified findings remaining: 0. Unclassified scanner findings remaining: 0. Review findings closed: 3. Review findings remaining: 4.
None
None
Expand | File | Group | Subtype | Severity | Status |
|---|
No findings available
0 findings
This skill answers: Who sees what variant?
In most cases, experiments work best with an equal split. If you want to limit exposure to the test variant, adjust the rollout percentage instead.
Why equal splits are better:
Always default to an equal split unless the user explicitly requests otherwise.
Uneven splits combined with the default "Exclude from analysis" handling can introduce bias. If the experiment observes multi-variant users (users exposed to more than one variant) then those are dropped asymmetrically — the smaller variant loses a larger fraction of its assignments. If those users behave differently from the rest, the smaller variant's metrics will be skewed.
The right mitigation depends on experiment state:
configuring-experiment-analytics
for how to set this. Note that "first seen" handling can introduce other biases, but it's
preferable to mid-run reassignment.There are two separate controls that determine who sees what.
Both live on the linked feature flag, sent through the feature_flag object in the flag's own shape (not the deprecated parameters keys).
feature_flag.filters.multivariate.variants)How users inside the experiment are distributed across variants.
{key, name, rollout_percentage}, where the rollout_percentage values must sum to 100"control" when present, else the first variantIf the user says "A/B/C test" without naming keys, key the baseline "control" (the convention) and create additional variants for the others; if they ask for specific keys, use them as-is with the baseline first.
feature_flag.filters.groups[0].rollout_percentage)What percentage of all users enter the experiment at all, sent as a single rollout group: groups: [{ "properties": [], "rollout_percentage": N }].
Default: 100%.
Users not included are excluded entirely: they don't see any variant and are not part of the analysis.
Both controls live inside feature_flag.filters:
filters may also carry aggregation_group_type_index (to run the experiment on a group type rather than individual users) and payloads (JSON-encoded strings keyed by variant key).
On a running experiment, any flag-config change must also send update_feature_flag_params: true, otherwise the API rejects the update before it reaches the flag (see "Changing rollout on a running experiment").
These two controls multiply:
| Overall rollout | Variant split | % seeing test | % in analysis |
|---|---|---|---|
100% | 50/50 | 50% | 100% |
100% | 75/25 control/test | 25% | 100% |
50% | 50/50 | 25% | 50% |
25% | 50/50 | 12.5% | 25% |
CRITICAL: If the user requests an uneven variant split (e.g. "60/40", "70/20/10") or mentions a specific percentage that could refer to either the split or the rollout (e.g. "roll out to 25%"), you MUST clarify before proceeding. This covers two cases:
The percentage is ambiguous — it could mean a variant split or a rollout change. Ask:
Adjust the numbers to match whatever percentage the user requested.
The ratio looks like an explicit variant split, but a reduced rollout with an equal split is almost always better. Explain the trade-off and recommend the alternative:
Adjust the numbers to match the ratio. For experiments with more than two variants, "equal" means each variant gets the same share (e.g. 34/33/33 for three variants). If the user confirms they want the uneven split after seeing the trade-off, proceed — but DO NOT skip the next section.
If the user proceeds with an uneven split (option 2 in either case above), you MUST surface the multivariate-handling implication BEFORE creating or updating the experiment. The user has chosen the riskier rollout path and needs to make an informed choice about how to mitigate.
Ask:
See configuring-experiment-analytics for how to set the multivariate handling. Apply the choice
as part of the same operation (creation or update) — do not leave the user with an uneven split
under default handling without an explicit, informed decision.
By default a person's variant follows their distinct ID. When the same person sees the flag before and after they are identified (usually at login), their distinct ID changes, and so can their variant. Choose before you create the flag or the experiment; never change bucketing or persistence on a live flag.
The share of anonymous visitors on a page does not settle this. It gives the mix on the page, not whether the same individuals cross identification there, and those are different questions: an even mix can be two populations that never meet, and an almost fully identified page can still route every one of those people through an anonymous first pageview. So present the three options with what each one needs, and let the user choose:
ensure_experience_continuity out, so the team's persistence default applies, except in the two cases below.ensure_experience_continuity: true): cannot work where an SDK evaluates the flag locally, because a local evaluation never consults the override store. It also needs person profiles for anonymous users, no bootstrapping, and $anon_distinct_id on server flag calls. Learn more: https://posthog.com/docs/feature-flags/creating-feature-flags#persisting-feature-flags-across-authentication-stepsWhen ensure_experience_continuity is omitted, experiment-create applies the team's persistence default. The device-id recipe needs no such step: create-feature-flag leaves persistence off unless you set it, and experiment-create rejects a feature_flag object for a flag that already exists. Device-id bucketing and persistence can't be combined, and the API refuses the pair.
Two cases need the field set rather than omitted:
experiment-create already made onto device-id bucketing. This applies only to a draft experiment whose flag is still inactive; for a launched experiment, keep the current bucketing and recommend ending and restarting the experiment through managing-experiment-lifecycle. That flag carries the team's persistence default, so pass ensure_experience_continuity: false in the same call, or the refusal above blocks the change.false only if they ask, because the team chose that default.experiment-create cannot set bucketing, so create the flag first, then link it:
Call create-feature-flag with the experiment's key, bucketing_identifier: "device_id", and active: false so no one gets a variant before launch (a new flag is active by default):
filters.groups is required: a flag without a group is rejected.
Call experiment-create with that feature_flag_key and no feature_flag object. The experiment links the flag as it is, and launching the experiment turns the flag on.
experiment-setup-context reports the facts these rules need (the page's share of unidentified visitors, and per SDK the device-ID and local-evaluation shares). creating-experiments (references/setup-decisions.md) applies them.
Rollout changes require an experiment ID. If the user refers to an experiment by name
or description (e.g. "change rollout on my signup test"), load the finding-experiments
skill to resolve it to a concrete ID before proceeding.
After resolving, show the matched experiment's name, ID, linked flag key, and status, and get user confirmation before making any change. If more than one experiment matches, ask the user to pick one.
Any change to rollout or variant split on a running experiment affects both user experience and statistical validity. You MUST warn the user and get explicit confirmation before making the change.
Do NOT silently apply the change — even if the user asked for it directly. Present the warning covering both perspectives:
Exception: Increasing rollout (without changing the split) is generally safe — no users switch variants, more users are added cleanly. A lighter warning is enough here, but still state the experiment ID and the current and new rollout percentages, and get explicit user confirmation before sending update_feature_flag_params: true.
If the goal is "stop new users from entering" rather than a percentage change: reducing the rollout is the wrong tool — it drops already-enrolled users out of the experiment too.
Freezing exposure (experiment-freeze-exposure) closes enrollment while enrolled users keep their variant and metrics keep flowing; see managing-experiment-lifecycle for its preconditions and limitations.
Mid-experiment fix for uneven-split bias: switching multivariate handling from "Exclude" to "First
seen variant" is the recommended mitigation for already-launched experiments — no users switch variants
and all collected data stays in the analysis. Changing the split to be even is an anti-pattern mid-run
(typically requires resetting or ending the experiment) and is only preferred if the experiment hasn't
been exposed to many users yet. See configuring-experiment-analytics for how to change the handling.
See references/changing-distribution-after-launch.md for detailed warnings, what to tell the user, and when to recommend alternatives.
configuring-experiment-analytics — the analysis side: exposure criteria, metrics, and multivariate handlingdiagnosing-experiment-health — when a mid-run split change has already skewed the resultsmanaging-experiment-lifecycle — reset or end-and-restart mechanics when a split change requires themGet started with chainctlchainctl skill install public/posthog/configuring-experiment-rollout:latest
Bring Chainguard's hardening pipeline to your first-party skills — custom rulesets, automated remediation, full audit trail.
skills.cgr.dev/public/github.com/posthog/skills/omnibus/configuring-experiment-rollout:latest
MIT
HARDENING.md
LICENSE
SKILL.md
references/changing-distribution-after-launch.md