Drift setup: the tiers, and what each one needs
Every requirement on this page fails silently if unmet — and a silent failure is indistinguishable from a clean bill of health. If your checks keep saying "no drift detected," this page is where to look.
The short version
| What it catches | What it needs | |
|---|---|---|
| Sessions & load map | Nothing on its own — it's the ground truth everything else scores against | Recorder snippet (or PostHog/Sentry) · ~30 recorded sessions |
| Component tier | A component carrying real user load being removed, renamed, or restructured | The PR head built and its manifest uploaded — one line of CI. No preview deploy. |
| Replay tier | Render-level breakage — an element that still exists in the code but is no longer reachable: clipped, covered, off-screen, unclickable | Additionally, a reachable preview deployment per PR |
The tiers stack. Each adds a class of problem the one below it cannot see.
1. Sessions and the load map
Connect a session source and let ~30 sessions accumulate — the load map computes automatically at 30, and refreshes itself as sessions keep arriving.
Watch for: a snippet that's in your deploy config but absent from the built page. View source on the deployed site — the app.uxsense.ai/api/r.js?id=… tag has to actually be there. A build that predates the environment variable looks perfectly healthy while recording nothing.
Returning visitors are not required. Three of the four signals compute from any sessions. Only stereotypy_risk (muscle memory) needs repeat visits — it stays quiet rather than guessing until it has enough of them. stereotypy: null in your first weeks means "warming up," not "broken."
2. Component tier — one line of CI, no preview
Drift compares the base commit's build manifest against the head commit's. Component identity is stamped at build time by @uxsense/stamp and hashed from component name, export name, and occurrence — never file path or line number — so renames and moves are tracked correctly.
Add the upload to the build you already run on pull requests:
- name: Upload behavioral manifest
env:
UXSENSE_API_KEY: ${{ secrets.UXSENSE_API_KEY }}
run: npx uxsense upload-manifest
UXSENSE_API_KEY comes from your project's Settings and goes in your CI provider's secret store. Requires @uxsense/stamp 0.1.2 or later — and note that npm ci installs exactly what your lockfile pins, so after upgrading, run npm install @uxsense/stamp@latest and commit the updated package-lock.json. A caret range alone is never consulted under npm ci.
Without this step there is no head manifest, the diff compares nothing, and every check runs in limited mode — it cannot see a component removed or renamed, however much load that component carries. Run the upload on pushes to your default branch too, so base manifests exist.
Expect two checks per push
The first check posts within seconds of the pull request event — before your CI has finished building — and reports Limited check — no build manifest for this commit. When the manifest arrives, a second, full-mode check supersedes it.
This is correct behavior, not flakiness. The first check is honest about what it couldn't see; the second replaces the story. On a slow build the gap may be several minutes.
3. Replay tier — preview deployments
Drift replays your users' recorded journeys against the deployed preview for a pull request. This is the only tier that catches render-level breakage: an element that still exists in the DOM and the code, but that a real user can no longer reach.
A CSS change is the archetype. A stylesheet edit changes no component stamp, so the manifest diff is empty and the component tier is structurally blind to it — correctly so, since nothing about the component changed.
Preview detection is zero-config for Vercel, Render previews, and anything posting GitHub Deployments (Netlify, Amplify, most custom CI), with a URL template fallback in Connections → Path replay previews.
If your journeys pass through a form (search, login, checkout), set Form test values in the same Connections blade — keyed by field label. Text fields get typed; dropdowns must match an option's visible text exactly (a non-matching value keeps the app's default). Without valid values, replays that can't get past the form report "unverified" — never a false path-broken.
Without previews you keep everything above, and render-level breakage is caught after deploy instead — the production corpus is measured, and it surfaces as a completion-share drop in your next impact report.
The one-line version: previews move detection from post-merge (measured) to pre-merge (prevented).
Why a change might report clean
In the order worth checking:
| Symptom | Cause | Fix |
|---|---|---|
Limited check — no build manifest for this commit persists after CI finishes | No upload step, or UXSENSE_API_KEY unset | §2 |
CI log says UXSENSE_API_KEY is not set | Secret missing from the CI provider | Add the secret |
CI log says Uploaded … stamps for <sha> where <sha> is not the PR head | @uxsense/stamp older than 0.1.2 keyed to the merge commit — check the lockfile, not just the range: npm ci installs the pinned version | Upgrade and commit the updated lockfile |
| Full mode, but a CSS-only change reads clean | Expected — no stamp changed | Needs the replay tier (§3) |
| No sessions, no load map | Snippet absent from the built page | View source on the deploy |
A note on the dry-run tool
check_drift (the MCP tool) is not a proxy for the merge-time check. It models any touched component as disturbed and resolves changed files against a single stored manifest, so:
- it may warn where the PR check reads clean — it is deliberately more conservative
- it cannot see renames, because the new file path resolves to no known identity
Use it to catch load-bearing edits before committing. Use the PR check for the verdict.
← All help articles