You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Moving a large subtree repeatedly within one mutation batch queues the same root for mirror cleanup multiple times. The recorder then walks the same final subtree for every queued entry before serializing additions.
This PR is stacked on #4807. Both benchmark builds already include the previous getter-cache and serialization-options improvements. This is another partial improvement for #4217, not a complete fix.
Changes
Replace the pending mirror-removal array with an insertion-ordered set of node references. Identical roots are queued once, while distinct parent/child roots remain separate.
Preserve first-seen cleanup order and consume each root before traversal, including when traversal throws. Cleanup still finishes before additions. Later emissions can queue the same root again.
Leave Mirror.removeNodeFromMap and emitted removal records unchanged. No live DOM values or masking decisions are cached.
Add regression tests for repeated moves, stable mirror IDs, later emissions, detached descendants and traversal errors.
Extend the opt-in benchmark with flat sibling lists, sibling reversal, repeated moves, whole-subtree removal/restoration and diagnostic mirror counters. Keep intermediate replay and trusted-input checkpoints.
Performance results
Three local runs per build and shape on an Apple M4 Pro, Chromium 136.0.7103.25, approximately 50k nodes, compression enabled and no CPU throttling. Baseline and candidate runs alternated using the same benchmark source. Profiling and diagnostic counters were disabled in timing comparisons.
Workload
Median longest task before / after
Median input delay before / after
Table: 11 moves in one batch
591 / 471 ms
597.0 / 477.3 ms
Flat: 11 moves in one batch
584 / 476 ms
591.3 / 483.3 ms
Flat: remove children
77 / 67 ms
77.5 / 67.9 ms
Table: reverse siblings
174 / 170 ms
221.5 / 216.7 ms
The repeated-move case is five round trips plus a final move. It deliberately stresses duplicate cleanup and is not a claim about typical customer behavior. Results are descriptive samples, not significance estimates. Single-subtree removal was effectively flat. Table restoration was slightly worse in these samples, at 154 / 164 ms longest task. Startup is not optimized.
A separate 10k-node diagnostic run showed 11 cleanup roots and 110,253 recursive visits before the change, versus one root and 10,023 visits afterward. Both visited the same 10,023 distinct physical nodes. The focused regression failed before the change with 11 root visits instead of one and passes afterward.
All 12 timing comparison arms passed replay, input, privacy and drop/recovery checks. The recorder artifact increased by 13 raw bytes, with no gzip size increase in this build.
Validation and risk
SDK/dependency and rrweb builds/typechecks passed.
Recording/accessor suites: 321 tests passed, 2 skipped, including existing iframe, shadow-DOM and lifecycle coverage.
Browser masking: 9 tests passed across Chromium, Firefox and WebKit.
Small table/flat/shadow/CSS ordering fixtures passed with both compression settings. The previous churn workload also passed with both settings.
A 10k-node, 4x page-throttled table/flat run passed with compression enabled.
A temporary negative probe dropped the reorder mutation and correctly failed an intermediate replay checkpoint.
Targeted lint/format, syntax and ES5/ES6 checks passed.
Isolated autoreview of 881c58a8ddd502fde75670890573ae40e01a757c against the parent branch reported no actionable findings.
The incident checker flagged serializer correctness. This does not change style serialization. Cleanup ordering, stable IDs and replay state are covered by the tests above. There are no new lazy-load contracts, persisted fields, masking policies, sampling, rotation or flush behavior. No recording-volume change is expected.
Recording remains synchronous. The repeated-move workload still blocks for roughly 470 ms at 50k nodes, so this does not bound main-thread work or resolve#4217.
Release info Sub-libraries affected
Libraries affected
All of them
posthog-js (web)
posthog-js-lite (web lite)
posthog-node
posthog-react-native
@posthog/react-native-plugin
@posthog/react
@posthog/ai
@posthog/convex
@posthog/next
@posthog/nextjs-config
@posthog/nuxt
@posthog/openfeature-node-provider
@posthog/openfeature-web-provider
@posthog/rollup-plugin
@posthog/webpack-plugin
@posthog/types
@posthog/browser-common
Checklist
Tests for new code
Accounted for the impact of any changes across different platforms
Accounted for backwards compatibility of any changes (no breaking changes!)
Took care not to unnecessarily increase the bundle size
If releasing new changes
Added a posthog-js patch changeset
🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Implemented with Pi using repository inspection, Chromium profiling, Vitest, Playwright and isolated autoreview. The change targets demonstrated duplicate cleanup only. It does not apply that rule to mutation preprocessing or ordering, where parent relationships need separate analysis.
The code formatter hook was skipped to preserve existing rrweb formatting rather than reformat entire files. Targeted formatting/lint checks and the other commit hooks passed. No hook configuration was changed. Human review is required.
This diff touches code involved in past incidents. This is a heads-up, not a verdict: read the matched sections of INCIDENTS.md and answer their review questions before merging.
For a judgment on whether this diff has the same failure mode, run the replay-incident-risk skill locally: open a Claude session in the repo root and ask it to review your diff with that skill. Or run node .agents/skills/replay-incident-risk/check.mjs and answer the review questions for each matched class yourself.
Matched 1 past incident pattern(s) (diff vs origin/perf/replay-mutation-preprocessing):
## rrweb serialization and snapshot correctness [path match]
Serializer bugs corrupt replays silently: no exception, no volume change. In May 2026 a cssText spec quirk silently dropped layout styles for sites using CSS custom properties. Test against framework-generated CSS (Chakra/Panda, Emotion speedy, Tailwind) in a real browser.
Touched paths:
- packages/rrweb/rrweb/src/record/mutation.ts
- packages/rrweb/rrweb/test/record/mutation-mirror-removal.test.ts
Read: .agents/skills/replay-incident-risk/INCIDENTS.md#class-4-rrweb-serialization-and-snapshot-correctness
This check is advisory. It flags resemblance to past incidents, not correctness.
For a judgment pass on whether this diff has the same failure mode, run the
`replay-incident-risk` skill in a Claude session from the repo root, or answer the
review questions in the matched sections of .agents/skills/replay-incident-risk/INCIDENTS.md yourself.
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
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
Moving a large subtree repeatedly within one mutation batch queues the same root for mirror cleanup multiple times. The recorder then walks the same final subtree for every queued entry before serializing additions.
This PR is stacked on #4807. Both benchmark builds already include the previous getter-cache and serialization-options improvements. This is another partial improvement for #4217, not a complete fix.
Changes
Mirror.removeNodeFromMapand emitted removal records unchanged. No live DOM values or masking decisions are cached.Performance results
Three local runs per build and shape on an Apple M4 Pro, Chromium 136.0.7103.25, approximately 50k nodes, compression enabled and no CPU throttling. Baseline and candidate runs alternated using the same benchmark source. Profiling and diagnostic counters were disabled in timing comparisons.
The repeated-move case is five round trips plus a final move. It deliberately stresses duplicate cleanup and is not a claim about typical customer behavior. Results are descriptive samples, not significance estimates. Single-subtree removal was effectively flat. Table restoration was slightly worse in these samples, at 154 / 164 ms longest task. Startup is not optimized.
A separate 10k-node diagnostic run showed 11 cleanup roots and 110,253 recursive visits before the change, versus one root and 10,023 visits afterward. Both visited the same 10,023 distinct physical nodes. The focused regression failed before the change with 11 root visits instead of one and passes afterward.
All 12 timing comparison arms passed replay, input, privacy and drop/recovery checks. The recorder artifact increased by 13 raw bytes, with no gzip size increase in this build.
Validation and risk
881c58a8ddd502fde75670890573ae40e01a757cagainst the parent branch reported no actionable findings.The incident checker flagged serializer correctness. This does not change style serialization. Cleanup ordering, stable IDs and replay state are covered by the tests above. There are no new lazy-load contracts, persisted fields, masking policies, sampling, rotation or flush behavior. No recording-volume change is expected.
Recording remains synchronous. The repeated-move workload still blocks for roughly 470 ms at 50k nodes, so this does not bound main-thread work or resolve #4217.
Release info Sub-libraries affected
Libraries affected
Checklist
If releasing new changes
posthog-jspatch changeset🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Implemented with Pi using repository inspection, Chromium profiling, Vitest, Playwright and isolated autoreview. The change targets demonstrated duplicate cleanup only. It does not apply that rule to mutation preprocessing or ordering, where parent relationships need separate analysis.
The code formatter hook was skipped to preserve existing rrweb formatting rather than reformat entire files. Targeted formatting/lint checks and the other commit hooks passed. No hook configuration was changed. Human review is required.