fix(record): recover CSS rules when another script displaces the insertRule patch - #38
Merged
Conversation
…rtRule patch CSS-in-JS rules only ever reached the replayer through the monkey patch on CSSStyleSheet.prototype.insertRule, since the <style> elements emotion and styled-components own hold no text and so have no copy of their contents beyond the _cssText taken at serialization. Any script that reinstalls a previously captured insertRule silently unhooks us and the replay is stuck with the CSS that existed at snapshot time; a production session kept 734 of 2929 emotion rules that way and replayed a MUI-heavy UI completely unstyled. Recording now probes that its patch still runs, reinstalls it over whatever displaced it, and re-sends the rules the replayer is missing. Teardown only unwinds its own layer so that stopping a recorder cannot blind another script that patched on top of it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Vadman97
marked this pull request as ready for review
August 11, 2026 20:25
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 3 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 9b21f4f. Configure here.
RobertPakkoLD
approved these changes
Aug 12, 2026
RobertPakkoLD
left a comment
There was a problem hiding this comment.
I would resolve the bugbot comments, but general approach seems fine to me.
abelonogov-ld
approved these changes
Aug 12, 2026
…ng stop Bugbot found three ways the recovery path could misbehave once more than one layer of patches is in play on CSSStyleSheet.prototype. Health is now judged per method. A script that reinstalls only insertRule -- the common case -- used to make us reinstall deleteRule as well, even though our delete patch was still in the call chain, so every later deletion was reported twice and the rule bookkeeping drifted. The proxies also guard against being chained over themselves: only the outermost of ours reports a call. That is what makes the identity based health check safe on browsers without constructed stylesheets, where a wrapper that calls through cannot be told apart from a displacement and we would otherwise stack a second insert proxy on top of the first. Finally, teardown cannot uninstall a patch another script has wrapped, so the patches now fall silent once recording has stopped rather than carrying on emitting stylesheet mutations into a recording that is supposed to be over. Co-Authored-By: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Vadim Korolik <vkorolik@launchdarkly.com>
Vadman97
added a commit
to launchdarkly/observability-sdk
that referenced
this pull request
Aug 13, 2026
…ks rrweb (#725) Bumps the rrweb submodule to [fe8256d5](launchdarkly/rrweb@fe8256d) ([launchdarkly/rrweb#38](launchdarkly/rrweb#38)) so recording recovers CSS-in-JS rules when another script on the page displaces rrweb's `CSSStyleSheet.prototype.insertRule` patch. A production session kept 734 of 2929 emotion rules that way and replayed a MUI-heavy UI completely unstyled. The README touch is what gives release-please a path inside `sdk/highlight-run/` to attribute the release to — same approach as #615. `highlight.run` gets a patch bump, and `@launchdarkly/session-replay` / `@launchdarkly/observability` follow via the node-workspace plugin. Also picks up launchdarkly/rrweb#36 and #37; both are replay-side and inert for the recorder bundles these packages ship. No dependency changes in the bumped range, so `yarn.lock` is untouched. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Overview** > Bumps the **launchdarkly/rrweb** submodule (via upstream PR #38) so session replay **periodically reconciles CSS-in-JS stylesheets** (emotion, styled-components) against what recording has already reported. That recovers style rules when another on-page script **replaces rrweb’s `CSSStyleSheet.prototype.insertRule` hook**, which previously left MUI-heavy replays largely unstyled. > > The **README** adds a short note describing that behavior and gives **release-please** a file under `sdk/highlight-run/` to attribute a patch release (same pattern as #615). Related rrweb replay-only fixes (#36, #37) ride along but do not change recorder bundles; **no `yarn.lock` changes** in the bumped range. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 7492160. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

CSS-in-JS rules only reached the replayer via the
CSSStyleSheet.prototype.insertRulepatch, so any script that reinstalls a previously capturedinsertRulesilently unhooks recording and the replay freezes with the CSS that existed at snapshot time. Recording now probes that its patch still runs, reinstalls it over whatever displaced it, re-sends the missing rules, and on teardown only unwinds its own layer.Found while investigating a production session that kept 734 of 2929 emotion rules and replayed a MUI-heavy modal completely unstyled.
🤖 Generated with Claude Code
Note
Overview
When another script restores a captured
CSSStyleSheet.prototype.insertRule, CSS-in-JS runtime rules stop reaching the recording and replay stays stuck at snapshot-time CSS. Recording now periodically probes whether its patches still run (via a throwaway constructed stylesheet), reinstalls displacedinsertRule/deleteRuleproxies over whatever is on the prototype, and re-emits missing rules as normalStyleSheetRuleevents.The new
styleSheetResyncIntervalrecord option controls that interval (default 2000 ms; 0 disables). Teardown usesrestorePatchedMethodso only rrweb’s layer is removed—stopping the recorder no longer strips another vendor’s wrapper; proxies that remain in the chain go silent via astoppedflag, with re-entrancy guards so chained self-repatches do not double-report rules.Puppeteer tests cover displacement, repatch, no duplicate resync, partial displacement, cooperative wrappers, and teardown behavior.
Reviewed by Cursor Bugbot for commit dfead46. Bugbot is set up for automated code reviews on this repo. Configure here.