onboarding: gate experiment assignment-context id until would-show#322777
Merged
benibenj merged 2 commits intoJun 24, 2026
Conversation
Onboarding experiments previously leaked their assignment-context identifier into telemetry from application startup — before the user had reached (or been eligible to reach) the onboarding experience — polluting the A/B scorecard with pre-onboarding activity. Scenarios can now declare an experiment with two treatment flags: a boolean `behaviorFlag` (treatment shows the tour, control does not) and a string `assignmentContextIdFlag` (the id the scorecard keys on, which must use the reserved `onb-` prefix). An eagerly-registered telemetry filter blocks any `onb-`-prefixed id from the very first event. When the user reaches the would-show moment the gate opens for that id (persisted APPLICATION/MACHINE so it survives reload/relaunch) and it flows on every event thereafter in both arms. The experiment is only active when both flags resolve and the id carries the prefix; a missing prefix is rejected and reported so a misconfiguration can never leak ungated. Also emits `onboarding.scenarioOutcome` telemetry when a tour is shown (outcome, dismiss reason, furthest step, step count, duration, experimentActive) and threads dismiss reason / furthest step through the spotlight presentation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Updates the onboarding scenario engine to support experiment-driven assignment-context gating, so onb- assignment-context identifiers don’t pollute telemetry until the user reaches the “would-show” moment, and enriches onboarding run results to capture dismiss reasons and progress for telemetry.
Changes:
- Replace the single experiment string with a two-flag experiment configuration (
behaviorFlag+assignmentContextIdFlag) and add the reservedonb-prefix contract. - Add an eager telemetry assignment filter and persisted “opened id” gate, and emit new
onboarding.scenarioOutcometelemetry based on richer presentation run results. - Thread richer run results (dismiss reason, furthest step, step count) through Spotlight presentation/overlay and expand unit tests for gating/persistence behaviors.
Show a summary per file
| File | Description |
|---|---|
| src/vs/workbench/contrib/onboarding/browser/onboardingService.ts | Implements experiment flag resolution, eager telemetry gating/filtering, gate persistence, and scenario outcome telemetry logging. |
| src/vs/workbench/contrib/onboarding/common/onboardingScenario.ts | Introduces experiment configuration interface, reserved assignment-context prefix, and richer run result/dismiss reason types. |
| src/vs/workbench/contrib/onboarding/common/onboardingPresentation.ts | Updates the presentation contract to return a structured run result instead of a bare outcome. |
| src/vs/workbench/contrib/onboarding/browser/spotlight/spotlightPresentation.ts | Produces structured run results (dismiss reason/progress) and handles “advance via target” telemetry details. |
| src/vs/workbench/contrib/onboarding/browser/spotlight/spotlightOverlay.ts | Emits richer step-end signals (advance source, skip reason) needed for dismiss-reason telemetry. |
| src/vs/workbench/contrib/onboarding/test/browser/onboardingService.test.ts | Adds tests for two-flag activation, prefix rejection, gate opening/persistence, and re-run contamination behavior. |
Copilot's findings
- Files reviewed: 6/6 changed files
- Comments generated: 3
…; clarify experiment docs Addresses PR review feedback: - Add IOnboardingRunResult.shown so the engine skips onboarding.scenarioOutcome telemetry for degenerate runs that render nothing (no steps / all steps skipped). - Clarify the IOnboardingExperiment doc: an inactive experiment means the scenario does not run automatically (manual runScenario still works, bypassing gating). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
roblourens
approved these changes
Jun 24, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Onboarding experiments attach their assignment-context identifier to every telemetry event from application startup — long before the user has seen (or been eligible to see) the onboarding experience. Because scorecards are computed from tagged telemetry, this pollutes the A/B baseline with a window of pre-onboarding activity the experiment could not have influenced, weakening retention/success signals for both arms.
Approach
An onboarding scenario can now declare an experiment with two pre-determined ExP treatment flags:
behaviorFlag(boolean) —trueshows the tour (treatment),falsedoes not (control).assignmentContextIdFlag(string) — the identifier the scorecard keys on. It must begin with the reservedonb-prefix.How it works:
onb--prefixed id from the very first event — solving the race where treatment flags resolve after the assignment context is already committed.onb-prefix. A non-empty id missing the prefix is rejected (treated as inactive) and reported viaonUnexpectedError, so a misconfiguration can never leak an ungated id.APPLICATION/MACHINE) so it survives reload/relaunch until the experiment stops. Treatment then shows the tour; control shows nothing and stays eligible.Telemetry
Adds
onboarding.scenarioOutcome(emitted only when a tour is actually shown):scenarioId,outcome,dismissReason(skipButton / escapeKey / targetClick / completed / aborted),lastStepIndex,stepCount,durationMs,experimentActive. The spotlight presentation now threads the dismiss reason and furthest step reached through its run result.Notes
newSessionTourwas intentionally not placed behind an experiment.runScenario(command/replay) bypasses gating and does not open the telemetry gate.Tests
onboardingService.test.tscovers: both-flags-required, prefix rejection, treatment-shows-and-opens-gate, control-opens-gate-but-no-show-and-stays-eligible, gate persists across reload, and re-run contamination. Validated withnpm run typecheck-client(clean) and the onboarding + spotlight unit tests (passing in Chromium).