fix(reo): stop a blocked analytics script from reporting as a Studio error - #1658
Merged
Conversation
…error `loadReoScript` rejects from the injected script's own `onerror` whenever an ad blocker blocks static.reo.dev. The chain in `useReo` had no rejection handler, so that became an unhandled rejection — and because the reject happens inside our bundle, the only stack frame is ours, which `shouldKeepEvent`'s reo.dev frame filter cannot attribute away. It reached Error Tracking as an unhandled Studio error: 14 events across 5 sessions (4.3% of sessions) in the 24h to 2026-08-27, against 0-1/day for the six days before. Catch and drop it. Reo is optional analytics and nothing degrades in its absence.
`useReo` is the first hook in `App.tsx` with no error boundary above it, so a synchronous throw from `loadReoScript` — argument validation in a future vendor release, DOM access in a hostile embedding — would escape the effect and blank Studio rather than degrade analytics. Start the chain from a resolved promise so both failure modes land in the one handler. Also pins the fix's premise in `shouldKeepEvent`: the loader rejection's only frame is the Studio bundle, so the reo.dev stack filter provably cannot suppress it and broadening that rule is not an alternative fix.
The case asserted only that the effect resolved. With the loader called inside `.then`, a synchronous throw becomes a rejection either way, so it stayed green with `.catch` deleted — it pinned the funnel but not the containment. Route it through `collectUnhandledRejections` so it fails for both mutations, and trim two comment sentences the code already states.
Contributor
There was a problem hiding this comment.
Code Review
This pull request refactors the useReo hook to safely load and initialize the Reo script within a promise chain, preventing unhandled promise rejections and synchronous throws from crashing the application. It also adds comprehensive unit tests for useReo and updates the Datadog integration tests. The review feedback suggests logging the caught error in the .catch block of useReo instead of silently swallowing it, ensuring better visibility for debugging.
Coverage Report
File Coverage
|
||||||||||||||||||||||||||||||||||||||
Addresses gemini's review note that a fire-and-forget chain should not swallow errors silently. Uses `console.debug` rather than the suggested `console.error`: the RUM SDK collects `console.error` as an error event, so that would send the failure straight back to the Error Tracking issue this catch exists to remove. Debug keeps a genuine Reo breakage visible to anyone with devtools open.
dawsontoth
marked this pull request as ready for review
August 27, 2026 15:35
The catch-path tests asserted only that no unhandled rejection escaped, so changing the catch to console.error kept them green while the RUM SDK collected that call and restored the Error Tracking issue the fix removes. Spy on console.error and console.debug: assert the failure reaches debug and that error is never called. Verified by mutation — swapping the catch to console.error turns all three catch-path tests red. 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.
Studio loads Reo.dev analytics through the bundled
reodotdevpackage. When an ad blocker blocks its CDN,loadReoScriptrejects from the injected script's ownonerror— and the chain inuseReohad no rejection handler, so it became an unhandled rejection. Because the reject happens inside our bundle, the rejection's only located frame isindex-*.js, which meansshouldKeepEvent's reo.dev stack filter cannot attribute it away: it reached Datadog Error Tracking as an unhandled Studio error.Found in the daily RUM review: 14 events across 5 sessions (4.3% of all sessions) in the 24h to 2026-08-27, against 0–1/day for the six days before, on the currently-served build (
index-DFE8mV3G.js). Together with a second third-party family this accounted for a third of the day's error-sessions while total error volume fell — errors got broader, not louder.This catches and drops the rejection (Reo is optional; nothing degrades without it), and starts the chain from a resolved promise so a synchronous throw from the vendor lands in the same handler —
useReo()is the first hook inApp.tsxwith no error boundary above it, so an escaping throw would blank Studio rather than degrade analytics.For the human reviewer
Chosen: silent.Settled during review: gemini flagged the empty catch and suggestedconsole.error. That specific channel is the one this PR cannot use — the RUM SDK instrumentsconsole.errorand reports it as a RUM error (error.source: "console", the same routeshouldKeepEvent.ts:93-97documents for React Query's handled AxiosErrors), so it would send the failure straight back into the Error Tracking issue this catch exists to remove. Nowconsole.debug, which RUM does not collect (datadogRumonly, nodatadogLogs), so a genuine Reo breakage is visible in devtools at zero RUM cost. Nothing left open here unless you disagree that debug is discoverable enough — the alternative is a taggeddatadogRum.addAction, deliberately not taken because it re-couples analytics failure to the error pipeline.shouldKeepEvent.tscould rule the other way. I ruled for the call site because the filter provably cannot do this job — it attributes on stack frames, and this stack's only frame is ours — so the filter alternative would have to match the message text, which that file's design deliberately avoids. The call site also covers the blank-screen case a filter cannot. The two fixes overlap and should not both land.Promise.resolve().then(...)vs.try { … } catch {}. Chosen: the resolved-promise funnel, so sync and async failure share one handler instead of two. Purely a legibility call; fully reversible.<script>. Chosen: mockedreodotdevplus a stack fixture inshouldKeepEvent.test.tspinning the premise. The realonerrorseam is inside the vendor package and no unit test reaches it; ifreodotdevever rejects from somewhere the wrapper does not cover, the suite stays green. Reversible by adding a jsdom test later.Verification
End-to-end route: not observable end-to-end from the repo — reproducing it needs a real ad blocker blocking
static.reo.devin a browser, and the resulting assertion is the absence of a Datadog event. Covered by unit tests at the seam instead, plus the production evidence above.npx vitest run— 327 files, 2709 passed, 11 skipped, exit 0.npx tsc -bexit 0;npx oxlint --format stylish .exit 0;npx dprint checkexit 0..catch(() => {})fails 3 of 6 cases; removing thePromise.resolve()funnel fails the sync-throw case. Restored, 6/6 green. The sync-throw case initially passed under the.catchmutation — round 2 of the review caught that, and it now routes throughcollectUnhandledRejectionsso it fails for both.src/integrations/google/gtm.tsandsrc/integrations/stripe/*were audited for the same unguarded-loader shape; Reo was the only remote-script loader with an uncaught chain.Complexity: easy
Review-Coverage: authored=claude; ran=codex; blocked=gemini(quota); declined=cursor-grok,cursor-composer,domain; rounds=4 @ 76614fb
Human-Review-Need: 3 @ 76614fb