fix: cached-render exposure read before identify() (SRM canary)#29
Conversation
The instant-cache path (PR #27) recorded $feature_flag_called via an async onFeatureFlags subscription, so on a repeat consented visit the read could land AFTER identifyConsented() reloaded flags under site_uuid (a different bucket) — recording exposure for the wrong bucket and breaking the SRM/stickiness canary. Per spec §3.1.4 the read is synchronous from PostHog's persisted flags, so it now runs inside prepareVariant before the function returns and therefore before identify() (flag-before-identify, §5.1). No onFeatureFlags subscription on the cache path → a post-identify reload can never re-record it. Adds tests/exposure-ordering.test.mjs pinning the invariant.
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository: wcpos/coderabbit/.coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (5)
📝 WalkthroughWalkthroughReplaces the async ChangesSynchronous Cached Exposure Recording
Shared JSON timestamp refresh
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Possibly related PRs
Poem
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0e1c0e1cf1
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const killValue = ph.getFeatureFlag(KILL_SWITCH_KEY, { send_event: false }); | ||
| if (killValue === true || killValue === 'on') clearCache(); |
There was a problem hiding this comment.
Preserve fresh kill-switch handling on cache hits
When the ops kill-switch is flipped after a visitor's last load, this cache path only reads the synchronously persisted value, so a stale false leaves wcpos_landing_assignment intact and the user gets at least one extra cached variant render after PostHog refreshes flags in the background. The previous cache-path subscription cleared the assignment when the fresh flag response arrived; keeping exposure synchronous is fine, but the kill-switch still needs a fresh, non-exposure (send_event: false) path so the kill takes effect on the next load as documented.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Right — fixed in #30. Added watchKillSwitch(ph): a background onFeatureFlags watch that re-reads the kill-switch with send_event: false (non-exposure, so it's safe after identify — the ops flag is bucket-agnostic) and clears the assignment cache so a freshly-flipped kill takes effect next load. The experiment-flag exposure read stays synchronous + pre-identify, so #29's invariant is intact.
…30) #29 — restore fresh kill-switch on cache hits. The synchronous exposure read only sees the persisted kill-switch value; a switch flipped after the last load wouldn't clear the cache. Add watchKillSwitch(ph): a background onFeatureFlags watch that re-reads the kill-switch with send_event:false (non-exposure, so no post-identify bucket concern — the ops flag is bucket-agnostic) and clears the assignment cache so the kill takes effect next load (spec §3.1.4). The experiment-flag exposure read stays synchronous + pre-identify. #23 — preview must not resolve production flags. Add advanced_disable_flags in preview so the Pages harness never fetches landing-variant (a live flag could otherwise win over the seeded assignment) and preview iframe loads don't hit the production flag endpoint. Verified both seeded variants render deterministically. Extends tests/exposure-ordering.test.mjs.
Fixes a reviewer-flagged bug in the instant-cache render (PR #27).
Bug: the cache path recorded
$feature_flag_calledvia an asynconFeatureFlagssubscription, so on a repeat consented visit the read could fire afteridentifyConsented()reloaded flags undersite_uuid— a different experiment bucket than theanon_idthe cached assignment was made in. That records exposure for the wrong bucket and breaks the SRM/stickiness canary (spec §3.1.4, §6.1).Fix: read the flag synchronously in
recordCachedExposure(ph)insideprepareVariant, before it returns and therefore beforeidentify()(flag-before-identify, §5.1). Spec §3.1.4 specifies exactly this — getFeatureFlag returns synchronously from PostHog's persisted flags after the first load wrote the cache. NoonFeatureFlagssubscription on the cache path, so a post-identify reload can never re-record it. Confirmed against the bundled posthog-js source that init() restores persisted flags synchronously.Cold path and proActive unaffected. Adds
tests/exposure-ordering.test.mjspinning the ordering invariant.🤖 Generated with Claude Code
Summary by CodeRabbit
Tests
Chores