Skip to content

fix(replay): guard main-thread WASM fallback in decompression worker - #70859

Draft
posthog[bot] wants to merge 2 commits into
masterfrom
posthog-code/replay-decompression-fallback-guard
Draft

fix(replay): guard main-thread WASM fallback in decompression worker#70859
posthog[bot] wants to merge 2 commits into
masterfrom
posthog-code/replay-decompression-fallback-guard

Conversation

@posthog

@posthog posthog Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Problem

When session replay's decompression Web Worker fails to start, DecompressionWorkerManager falls back to initializing the snappy WASM module on the main thread. That fallback (initSnappy()snappyInit()) fetches the WASM binary over the network, which can fail (offline, ad blocker, transient CDN or static-asset hiccup, or the same network problem that killed the worker in the first place).

The fallback call was unguarded. When the fetch failed the rejection escaped: readyPromise rejected as an uncaught exception, and the replay_worker_init_failed capture on the following lines never fired. The worker-init fallback is a well-traveled path, so it's worth hardening even though the uncaught error itself is rare.

Changes

  • Wrap the fallback await this.initSnappy() in try/catch, mirroring the existing decompressWithFallback pattern.
  • Capture the failed WASM fetch as replay_snappy_init_failed telemetry instead of letting it surface as an uncaught error.
  • Track the failure in a snappyInitFailed flag so decompress() degrades cleanly (rejects with a clear "Decompression unavailable" error the caller already handles) rather than throwing a cryptic WASM error.
  • Move the replay_worker_init_failed capture ahead of the fallback attempt so it always records when the worker fails, not only when the fallback succeeds.

No user-visible UI change: this is a robustness fix on an error path.

How did you test this code?

Added two Jest cases to DecompressionWorkerManager.test.ts exercising the real manager (the existing suite ran only against the global no-op mock, so it never touched this path):

  • Worker init fails but snappy fallback succeeds: decompresses on the main thread, captures replay_worker_init_failed, does not capture replay_snappy_init_failed. Guards that reordering the capture didn't regress the common recovery path.
  • Both worker init and the snappy WASM fetch fail: readyPromise settles instead of rejecting uncaught, decompress() rejects cleanly with "Decompression unavailable", and both replay_worker_init_failed and replay_snappy_init_failed are captured. This is the exact regression the fix addresses.

Ran the suite locally (jest DecompressionWorkerManager.test.ts) — all 10 tests pass. TypeScript check and format/lint pass on the changed files. I (an agent) did not manually reproduce the offline/blocked-fetch state in a browser.

Automatic notifications

  • Publish to changelog?
  • Alert Sales and Marketing teams?

Docs update

No docs affected.

🤖 Agent context

Autonomy: Fully autonomous

Investigated and fixed by Claude (Claude Code) from an inbox report flagging a rare uncaught TypeError: Failed to fetch on session replay decompression. Root cause was the unguarded fallback initSnappy() in DecompressionWorkerManager.initWorker. Invoked the /writing-tests skill before adding tests — the gate confirmed the double-failure case catches a regression (removing the try/catch) that no existing test covered, since the existing suite runs entirely against the globally-mocked manager. Used jest.requireActual plus a controllable snappy-wasm mock and a throwing Worker global to exercise the real fallback path.

Agent-authored; requires human review.


Created with PostHog Code from an inbox report.

When the decompression Web Worker fails to start, DecompressionWorkerManager
falls back to initializing snappy WASM on the main thread. That fallback fetches
the WASM binary over the network, which can fail (offline, ad blocker, transient
CDN hiccup). The call was unguarded, so a failed fetch rejected readyPromise as
an uncaught exception, and the replay_worker_init_failed capture that followed
never fired.

Wrap the fallback initSnappy() in try/catch, capture it as replay_snappy_init_failed
telemetry, and settle readyPromise into a defined failure state so later
decompress() calls reject cleanly instead of throwing an uncaught error. The
worker-init capture now fires before the fallback attempt so it always records.

Generated-By: PostHog Code
Task-Id: 8aec8170-78b3-4128-905f-6a3aee9e331d
@github-actions

github-actions Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

🤖 CI report

Bundle size — 🟢 -104.5 KiB (-0.2%)

Uncompressed size of every built .js bundle, compared against the base branch.

Total: 64.66 MiB · 🟢 -104.5 KiB (-0.2%)

File Size Δ vs base
posthog-app/src/scenes/onboarding/Onboarding.js 47.0 KiB 🟢 -749.0 KiB (-94.1%)
exporter/_parent/products/alerts/frontend/views/EditAlertModal.js 119.4 KiB 🔺 +117.9 KiB (+8078.4%)
render-query/src/render-query/render-query.js 21.35 MiB 🔺 +99.9 KiB (+0.5%)
exporter/src/queries/schema.js 1.10 MiB 🔺 +95.1 KiB (+9.2%)
posthog-app/src/queries/schema.js 1.10 MiB 🔺 +95.1 KiB (+9.2%)
exporter/src/exporter/scenes/ExporterNotebookScene.js 3.11 MiB 🟢 -92.2 KiB (-2.8%)
posthog-app/src/scenes/quickstart/Quickstart.js 78.0 KiB 🔺 +78.0 KiB (new)
posthog-app/src/scenes/inbox/InboxScene.js 681.9 KiB 🔺 +22.9 KiB (+3.5%)
posthog-app/src/scenes/heatmaps/scenes/heatmap/HeatmapNewScene.js 28.5 KiB 🔺 +22.6 KiB (+378.5%)
posthog-app/_parent/products/pulse/frontend/PulseScene.js 17.5 KiB 🔺 +17.5 KiB (new)
posthog-app/_parent/products/tracing/frontend/TracingScene.js 87.0 KiB 🟢 -13.0 KiB (-13.0%)
posthog-app/src/scenes/heatmaps/scenes/heatmap/HeatmapRecordingScene.js 3.8 KiB 🟢 -12.5 KiB (-76.6%)
posthog-app/_parent/products/stamphog/frontend/scenes/StamphogScene/StamphogScene.js 11.8 KiB 🔺 +11.8 KiB (new)
posthog-app/src/scenes/experiments/Experiment.js 234.1 KiB 🔺 +11.8 KiB (+5.3%)
posthog-app/_parent/products/replay_vision/frontend/replay_scanners/ReplayScanner.js 94.6 KiB 🔺 +11.8 KiB (+14.3%)
posthog-app/_parent/products/workflows/frontend/WorkflowsScene.js 74.6 KiB 🔺 +11.3 KiB (+17.9%)
posthog-app/_parent/products/alerts/frontend/views/InsightAlerts.js 11.3 KiB 🔺 +11.3 KiB (new)
posthog-app/_parent/products/cohorts/frontend/staff/CohortsStaffToolsScene.js 11.1 KiB 🔺 +11.1 KiB (new)
posthog-app/_parent/products/engineering_analytics/frontend/scenes/EngineeringAnalyticsScene.js 70.4 KiB 🔺 +10.8 KiB (+18.1%)
exporter/src/exporter/scenes/ExporterDashboardScene.js 282.7 KiB 🔺 +10.3 KiB (+3.8%)
posthog-app/src/scenes/hog-functions/HogFunctionScene.js 51.3 KiB 🟢 -10.2 KiB (-16.6%)
posthog-app/src/scenes/App.js 32.1 KiB 🔺 +9.7 KiB (+43.1%)
posthog-app/_parent/products/posthog_ai/frontend/scenes/TaskTracker/components/SidePanelRunnerImpl.js 16.8 KiB 🔺 +9.6 KiB (+134.2%)
posthog-app/src/scenes/max/messages/adapters/SearchSessionRecordingsWidget.js removed 🟢 -9.3 KiB (-100.0%)
posthog-app/src/scenes/max/messages/adapters/ErrorTrackingWidget.js removed 🟢 -9.3 KiB (-100.0%)
posthog-app/_parent/products/posthog_ai/frontend/components/tool/widgets/ErrorTrackingWidget.js 9.1 KiB 🔺 +9.1 KiB (new)
posthog-app/src/scenes/max/messages/adapters/CreateInsightWidget.js removed 🟢 -8.7 KiB (-100.0%)
posthog-app/src/scenes/max/messages/adapters/QueryWidget.js removed 🟢 -8.6 KiB (-100.0%)
posthog-app/_parent/products/logs/frontend/components/LogsViewer/LogsViewerModal/LogsViewerModal.js 11.7 KiB 🔺 +8.3 KiB (+247.5%)
posthog-app/_parent/products/engineering_analytics/frontend/scenes/EngineeringAnalyticsTeamScene.js 8.2 KiB 🔺 +8.2 KiB (new)
posthog-app/_parent/products/posthog_ai/frontend/scenes/TaskTracker/TaskTracker.js 27.1 KiB 🔺 +8.1 KiB (+42.9%)
posthog-app/_parent/products/posthog_ai/frontend/components/tool/widgets/QueryWidget.js 8.0 KiB 🔺 +8.0 KiB (new)
posthog-app/_parent/products/posthog_ai/frontend/components/tool/widgets/CreateInsightWidget.js 8.0 KiB 🔺 +8.0 KiB (new)
posthog-app/_parent/products/review_hog/frontend/CodeReviewScene.js 52.9 KiB 🔺 +7.7 KiB (+16.9%)
posthog-app/_parent/products/metrics/frontend/MetricsScene.js 46.5 KiB 🔺 +7.3 KiB (+18.7%)
posthog-app/_parent/products/managed_migrations/frontend/ManagedMigration.js 24.1 KiB 🔺 +7.3 KiB (+43.7%)
posthog-app/_parent/products/ai_observability/frontend/AIObservabilitySessionScene.js 11.6 KiB 🔺 +7.2 KiB (+161.0%)
posthog-app/_parent/products/replay_vision/frontend/replay_scanners/ScannerEditorScene.js 23.6 KiB 🟢 -6.7 KiB (-22.3%)
posthog-app/src/scenes/AuthenticatedShell.js 215.7 KiB 🟢 -6.7 KiB (-3.0%)
posthog-app/_parent/products/workflows/frontend/Workflows/WorkflowScene.js 125.8 KiB 🔺 +5.8 KiB (+4.8%)
posthog-app/_parent/products/logs/frontend/scenes/LogsAlertDetailScene/LogsAlertDetailScene.js 25.5 KiB 🔺 +5.8 KiB (+29.3%)
posthog-app/_parent/products/mcp_analytics/frontend/MCPAnalyticsScene.js 140.4 KiB 🟢 -5.5 KiB (-3.8%)
posthog-app/_parent/products/alerts/frontend/AlertsScene.js 4.7 KiB 🔺 +4.7 KiB (new)
posthog-app/_parent/products/customer_analytics/frontend/CustomerAnalyticsScene.js 125.0 KiB 🔺 +4.3 KiB (+3.5%)
posthog-app/_parent/products/posthog_ai/frontend/components/tool/widgets/SearchSessionRecordingsWidget.js 4.1 KiB 🔺 +4.1 KiB (new)
posthog-app/_parent/products/conversations/frontend/scenes/ticket/SupportTicketScene.js 46.2 KiB 🔺 +4.1 KiB (+9.8%)
posthog-app/_parent/products/tracing/frontend/TracingOperationScene.js 15.7 KiB 🔺 +3.8 KiB (+31.4%)
posthog-app/_parent/products/data_warehouse/frontend/scenes/SchemaScene/SchemaScene.js 38.9 KiB 🔺 +3.8 KiB (+10.7%)
posthog-app/_parent/products/posthog_ai/frontend/components/tool/builtinToolRenderers.js 9.5 KiB 🔺 +3.6 KiB (+61.3%)
posthog-app/_parent/products/error_tracking/frontend/scenes/ErrorTrackingFingerprintScene/ErrorTrackingFingerprintScene.js 3.3 KiB 🔺 +3.3 KiB (new)
posthog-app/_parent/products/logs/frontend/components/LogsAlerting/LogsAlertingSection.js 3.2 KiB 🔺 +3.2 KiB (new)
posthog-app/_parent/products/ai_observability/frontend/AIObservabilityScene.js 121.0 KiB 🔺 +3.1 KiB (+2.6%)
posthog-app/_parent/products/ai_observability/frontend/evaluations/AIObservabilityEvaluation.js 58.2 KiB 🟢 -2.5 KiB (-4.1%)
posthog-app/_parent/products/logs/frontend/LogsScene.js 27.4 KiB 🔺 +2.3 KiB (+9.3%)
posthog-app/_parent/products/posthog_ai/frontend/components/tool/widgets/CreateNotebookWidget.js 2.3 KiB 🔺 +2.3 KiB (new)
posthog-app/src/scenes/max/messages/adapters/CreateNotebookWidget.js removed 🟢 -2.2 KiB (-100.0%)
posthog-app/_parent/products/posthog_ai/frontend/components/tool/EditDiffRenderer.js 1.6 KiB 🟢 -2.2 KiB (-58.2%)
posthog-app/_parent/products/ai_observability/frontend/prompts/LLMPromptScene.js 41.9 KiB 🔺 +2.1 KiB (+5.3%)
posthog-app/_parent/products/posthog_ai/frontend/components/tool/widgets/UpsertDashboardWidget.js 2.0 KiB 🔺 +2.0 KiB (new)
posthog-app/src/scenes/max/messages/adapters/UpsertDashboardWidget.js removed 🟢 -2.0 KiB (-100.0%)
posthog-app/_parent/products/ai_observability/frontend/evaluations/EvaluationTemplates.js 2.9 KiB 🔺 +2.0 KiB (+216.3%)
posthog-app/src/scenes/feature-flags/FeatureFlag.js 96.6 KiB 🔺 +1.9 KiB (+2.0%)
posthog-app/_parent/products/error_tracking/frontend/scenes/ErrorTrackingScene/ErrorTrackingScene.js 47.7 KiB 🔺 +1.9 KiB (+4.1%)
exporter/src/exporter/scenes/ExporterInterviewScene.js 304.6 KiB 🔺 +1.7 KiB (+0.5%)
posthog-app/_parent/products/error_tracking/frontend/scenes/ErrorTrackingIssueScene/ErrorTrackingIssueScene.js 104.5 KiB 🔺 +1.6 KiB (+1.5%)
posthog-app/src/scenes/data-pipelines/legacy-plugins/LegacyPluginScene.js 20.9 KiB 🟢 -1.5 KiB (-6.7%)
posthog-app/_parent/products/ai_observability/frontend/AIObservabilityTraceScene.js 133.0 KiB 🔺 +1.4 KiB (+1.1%)
posthog-app/src/scenes/heatmaps/scenes/heatmap/HeatmapScene.js 9.9 KiB 🟢 -1.4 KiB (-12.1%)
posthog-app/src/scenes/max/Max.js 23.9 KiB 🔺 +1.3 KiB (+5.6%)
posthog-app/_parent/products/mcp_analytics/frontend/MCPAnalyticsToolDetail.js 18.2 KiB 🔺 +1.2 KiB (+7.2%)
posthog-app/src/scenes/surveys/wizard/SurveyWizard.js 68.6 KiB 🟢 -1.2 KiB (-1.7%)
posthog-app/src/scenes/session-recordings/player/modal/SessionPlayerModal.js 11.0 KiB 🔺 +1.2 KiB (+11.8%)
posthog-app/src/scenes/saved-insights/SavedInsights.js 3.6 KiB 🟢 -1.0 KiB (-22.0%)
posthog-app/src/scenes/insights/InsightQuickStart/InsightQuickStart.js 8.2 KiB 🟢 -1020 B (-10.8%)
exporter/src/lib/components/ActivityLog/describers.js 129.4 KiB 🔺 +1019 B (+0.8%)
posthog-app/_parent/products/customer_analytics/frontend/scenes/CustomerJourneyTemplatesScene/CustomerJourneyTemplatesScene.js 10.1 KiB 🟢 -1018 B (-8.9%)

Posted automatically by build-bundle-size-report · uncompressed bytes from dist-report

Eager graph — within budget

How much code each root ships on the eager path — downloaded and parsed before the surface is interactive. Measured from the esbuild output chunks (post-tree-shake, static imports only); lazy import() / React.lazy chunks are not counted.

Root Eager (shipped) Δ vs base Budget
entry (logged-out pages, app bootstrap)
src/index.tsx
1.24 MiB · 22 files 🔺 +12.4 KiB (+1.0%) ███░░░░░░░ 27.5% of 4.51 MiB
authenticated shell (every logged-in page)
src/scenes/AuthenticatedShell.tsx
8.21 MiB · 3,000 files 🔺 +61.4 KiB (+0.7%) ████████░░ 84.5% of 9.71 MiB

🟢 node_modules/monaco-editor/ stays out of src/index.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/index.tsx
🟢 [object Object] stays out of src/index.tsx
🟢 [object Object] stays out of src/index.tsx
🟢 node_modules/monaco-editor/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.tsx

Largest files eagerly shipped from src/index.tsx
Size File
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
24.6 KiB ../node_modules/.pnpm/buffer@6.0.3/node_modules/buffer/index.js
6.3 KiB ../node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react.production.min.js
4.5 KiB ../node_modules/.pnpm/@jspm+core@2.1.0/node_modules/@jspm/core/nodelibs/browser/process.js
3.9 KiB ../node_modules/.pnpm/scheduler@0.23.2/node_modules/scheduler/cjs/scheduler.production.min.js
1.4 KiB ../node_modules/.pnpm/base64-js@1.5.1/node_modules/base64-js/index.js
1.3 KiB src/RootErrorBoundary.tsx
912 B ../node_modules/.pnpm/ieee754@1.2.1/node_modules/ieee754/index.js
789 B src/scenes/ChunkLoadErrorBoundary.tsx
762 B src/index.tsx
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx
Size File
281.3 KiB ../node_modules/.pnpm/posthog-js@1.406.2/node_modules/posthog-js/dist/rrweb.js
267.7 KiB ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
236.0 KiB src/taxonomy/core-filter-definitions-by-group.json
224.7 KiB ../node_modules/.pnpm/posthog-js@1.406.2/node_modules/posthog-js/dist/module.js
167.1 KiB src/queries/validators.js
154.3 KiB ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
105.8 KiB src/lib/api.ts
94.0 KiB ../packages/quill/packages/quill/dist/index.js
93.3 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js

Posted automatically by check-eager-graph · sizes are eager output bytes (shipped, post-tree-shake) from the esbuild metafile · part of #32479

⚠️ Dist folder size — 🔺 +42.60 MiB (+3.2%)

Total size of the built frontend/dist folder (all assets), compared against the base branch.

Total: 1357.17 MiB · 🔺 +42.60 MiB (+3.2%)

toolbar-size — eager 2.18 MiB within budget

What the toolbar ships to customer pages, measured from the esbuild output (minified, post-tree-shake). The eager set is the entry plus everything statically imported from it — fetched before any feature runs; deferred chunks load lazily. The eager guardrail is 5.72 MiB. Each output file must also stay below 10 MB, where CloudFront stops compressing it. The module boundary is enforced separately by check-toolbar-graph.

Metric Size Δ vs base Budget
Eager (shipped)
entry + static imports
2.18 MiB · 17 files (no base measurement) ████░░░░░░ 38.1% of 5.72 MiB
Deferred (lazy) 2.07 MiB · 33 files (no base measurement) n/a — loads on demand
Loader dist/toolbar.js 1.1 KiB (no base measurement) █░░░░░░░░░ 5.8% of 19.5 KiB
Largest eagerly-shipped chunks
Size File
713.8 KiB dist/toolbar/toolbar-app-HY7HJI4V.css
543.6 KiB dist/toolbar/chunk-chunk-UG3THN3N.js
484.2 KiB dist/toolbar/chunk-chunk-QS5AHYGW.js
133.6 KiB dist/toolbar/chunk-chunk-MCXISDMN.js
131.8 KiB dist/toolbar/chunk-chunk-T5KY5WYR.js
71.0 KiB dist/toolbar/toolbar-app-UDDFB6JG.js
69.0 KiB dist/toolbar/chunk-chunk-27JL52RE.js
35.6 KiB dist/toolbar/chunk-chunk-XVKSNBZ7.js
20.9 KiB dist/toolbar/chunk-chunk-CS7W2KTV.js
12.2 KiB dist/toolbar/chunk-chunk-PIK3PADE.js

Posted automatically by check-toolbar-size · sizes are toolbar output bytes (shipped, post-tree-shake) from the esbuild metafile

@trunk-io

trunk-io Bot commented Jul 14, 2026

Copy link
Copy Markdown

Static BadgeStatic BadgeStatic BadgeStatic Badge

View Full Report ↗︎Docs

…-guard

Generated-By: PostHog Code
Task-Id: 8aec8170-78b3-4128-905f-6a3aee9e331d
@posthog

posthog Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

Heads up for reviewers on the red check: Visual regression tests - chromium (2/5) is failing on two stories unrelated to this PR:

  • products/customer_analytics/frontend/components/Accounts/AccountsTab.stories.tsxRowExpandedUsagePopulated play-test (jest 60s test timeout)
  • Insights/Paths EditScene play-test → POST /api/projects/:id/metalytics/ returns 405 (missing Storybook MSW mock)

This PR only changes session-replay snapshot decompression (DecompressionWorkerManager.ts), which has no code path into either of those scenes. The failures reproduced identically across three runs, and I merged master in (which includes the recent expanded-row storybook fix #70243) — they still fail, so this is a master-level storybook issue rather than a regression from this diff. I've left those stories untouched to avoid stepping outside this PR's scope.

@scheduled-actions-posthog

Copy link
Copy Markdown
Contributor

This PR hasn't seen activity in a week! Should it be merged, closed, or further worked on? If you want to keep it open, please remove the stale label – otherwise this will be closed in another week. If you want to permanently keep it open, use the waiting label.

@trunk-io

trunk-io Bot commented Jul 28, 2026

Copy link
Copy Markdown

Merging to master in this repository is managed by Trunk.

  • To merge this pull request, check the box to the left or comment /trunk merge below.

After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants