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
{{ message }}
Repository navigation
[Due for payment 2026-06-01] [$250] [Sentry: APP-6K1] FullstoryInitHandler unhandled rejection ("Shutdown called") crashes web app #91290
The FullStory snippet enters a shutdown state in some sessions (ad-blocker, GDPR / Do-Not-Track opt-out, user-killed snippet, post-FS('shutdown') call). The .then(...) chain in FullstoryInitHandler has no .catch, so the promise rejection bubbles up as an unhandled error from inside the useEffect's passive mount commit phase, which React surfaces as a top-level crash.
One-line fix
Add a .catch to swallow (and optionally log) the rejection:
This is a recoverable, expected condition for any user whose FullStory session is shut down — we never need the URL in that case.
Reproduction
Force the FullStory snippet into a shutdown state (e.g. call FS('shutdown') from the devtools console, or load with an ad-blocker that kills edge.fullstory.com/s/fs.js)
Navigate so that FullstoryInitHandler re-mounts
App crashes with the error overlay (Uh-oh, something went wrong!)
FullstoryInitHandler.tsx:18 calls FS.getSessionURL().then(...) without a .catch(). When FullStory is in a shutdown state (ad-blocker, DNT opt-out, or explicit FS('shutdown') call), the underlying FullStory('getSessionAsync') rejects with "Shutdown called". The unhandled rejection bubbles up through React's passive effect commit phase and crashes the app.
Notably, isInitialized() still returns true after shutdown, so getSessionURL proceeds to call into a dead FullStory instance instead of bailing out.
What changes do you think we should make in order to solve the problem?
Add a .catch() to the getSessionURL() promise chain in FullstoryInitHandler.tsx:18-24 to gracefully handle the rejection. The session URL is purely informational (sets Sentry context) — failure is always safe to swallow with a warning log.
What alternative solutions did you explore? (Optional)
An alternative would be tracking shutdown state inside src/libs/Fullstory/index.ts (e.g., a hasShutdown flag set alongside the FS('shutdown') call at line 66) and returning early from getSessionURL when set. However, the .catch() approach is simpler, covers any rejection reason (not just shutdown), and matches the fact that this call is best-effort.
Supporting evidence
The useEffect in FullstoryInitHandler (lines 16-24) has no error handling on the promise chain.
FS.init() on web is a no-op, so calling it before getSessionURL provides no guard.
consentAndIdentify calls FS('shutdown') at line 66 when shouldInitialize returns false, but isInitialized() from @fullstory/browser remains true after shutdown.
Sentry shows 9,396 affected users / 17,283 events, confirming this is a frequent production crash.
Next Steps for Contributor+ team: Reply with @MelvinBot implement this to create a draft PR, @MelvinBot <your feedback> to refine this analysis, or explain why you are rejecting Melvin's proposal.
Please re-state the problem that we are trying to solve in this issue.
On web, the app can crash while FullstoryInitHandler is mounting if the FullStory browser snippet rejects getSessionAsync with Error: Shutdown called.
What is the root cause of that problem?
src/FullstoryInitHandler.tsx calls FS.getSessionURL().then(...) inside the useEffect that runs when user metadata changes, but it never handles a rejected promise.
On web, FS.getSessionURL() is a thin wrapper around FullStory('getSessionAsync', {format: 'url'}). If the FullStory snippet is already initialized but in a shutdown state, for example after FullStory('shutdown'), an opt-out/ad-blocker path, or another snippet shutdown condition, that async FullStory call rejects instead of returning a URL. Because the effect chain has no .catch, the rejection becomes an unhandled promise rejection during React's passive effect mount, and the web app surfaces it as a top-level crash.
What changes do you think we should make in order to solve the problem?
Handle the recoverable FullStory lookup failure in FullstoryInitHandler.
Concretely, import Log in src/FullstoryInitHandler.tsx and add a .catch after the existing FS.getSessionURL().then(...) chain:
FS.getSessionURL().then((url)=>{if(!url){return;}Sentry.setContext(CONST.TELEMETRY.CONTEXT_FULLSTORY,{url});}).catch((error)=>Log.warn('[FullstoryInitHandler] Failed to get FullStory session URL',{error}));
This keeps the useful Sentry context when FullStory returns a URL, but treats a missing/shutdown FullStory session as non-fatal. We do not need to block rendering or crash the app just because telemetry context cannot be attached.
Scope boundaries: this should only change the FullStory session URL lookup used for Sentry context in FullstoryInitHandler. It should not change FullStory initialization, consent/identity behavior, Sentry initialization, or native FullStory behavior.
What alternative solutions did you explore? (Optional)
Catching inside src/libs/Fullstory/index.ts would also prevent this specific rejection, but it would hide failures from every caller of getSessionURL(). Handling it in FullstoryInitHandler is better scoped because this caller explicitly treats the URL as optional context. Checking isInitialized() is already done in the wrapper, so the missing piece is rejection handling for the initialized-but-shutdown state.
Please re-state the problem that we are trying to solve in this issue.
On web, the app can crash with the "Uh-oh, something went wrong!" error overlay because of an unhandled promise rejection (Error: Shutdown called) originating from FullstoryInitHandler. This is a high-volume Sentry crash affecting thousands of users whenever the FullStory session is in a shutdown state (ad-blocker that kills fs.js, GDPR / Do-Not-Track opt-out, or a prior FS('shutdown') call).
What is the root cause of that problem?
FullstoryInitHandler calls FS.getSessionURL() inside a useEffect and attaches only a .then() handler — there is no .catch():
getSessionURL() resolves by delegating to the FullStory browser SDK. It only short-circuits when isInitialized() is false; once the snippet has initialized it calls FullStory('getSessionAsync', {format: 'url'}):
The problem is that isInitialized() still returns true for a session that has initialized and then been shut down. In that state getSessionAsync rejects with Error: Shutdown called. Because the .then() chain in FullstoryInitHandler has no rejection handler, the rejection escapes during React's passive-effect mount commit phase and is surfaced as a top-level unhandled rejection, which trips the global error boundary and crashes the web app.
This is an expected, recoverable condition: when the FullStory session is shut down we simply have no session URL to attach to the Sentry context, and the app should continue normally.
What changes do you think we should make in order to solve the problem?
Attach a rejection handler to the FS.getSessionURL() promise chain in FullstoryInitHandler so a shutdown (or any other) rejection is caught instead of bubbling up as an unhandled rejection.
Concretely, in src/FullstoryInitHandler.tsx add a .catch() to the existing FS.getSessionURL().then(...) chain at the line referenced above. The catch handler should swallow the rejection and log it at warn level (e.g. via Log.warn from src/libs/Log) so the failure is still observable in telemetry without crashing the app. When the session URL is unavailable we already skip setting the Sentry context (the existing if (!url) return; guard), so no Sentry context is set in the shutdown case — which is the correct behavior since there is no URL to record.
This keeps the fix scoped to the single component that owns the unguarded promise, leaves the FullStory wrapper's existing isInitialized() short-circuit untouched, and resolves the crash for every shutdown scenario listed in the report (ad-blocker, opt-out, manual shutdown).
What alternative solutions did you explore? (Optional)
Adding the try/catch (or .catch()) inside getSessionURL() in src/libs/Fullstory/index.ts was considered, but that wrapper is intentionally a thin pass-through to the SDK and other callers may want to observe the rejection; catching at the FullstoryInitHandler call site keeps the error-handling decision local to the consumer that doesn't need the URL.
🚨 Edited by proposal-police: This proposal was edited at 2026-05-21 11:47:25 UTC.
Proposal
Please re-state the problem that we are trying to solve in this issue.
On web, when the FullStory snippet enters a shutdown state (ad-blocker, GDPR/Do-Not-Track opt-out, or a prior FS('shutdown') call), FullstoryInitHandler triggers an unhandled promise rejection (Error: Shutdown called) that React surfaces during the passive-effect commit phase as a top-level crash with the "Uh-oh, something went wrong!" overlay (Sentry APP-6K1: 9,396 users / 17,283 events). Shutdown is an expected, recoverable condition for these users — the app should silently skip setting the Sentry context and keep running.
with no .catch. The web implementation at src/libs/Fullstory/index.ts#L98-L103 delegates to FullStory('getSessionAsync', {format: 'url'}), which rejects with Error: Shutdown called whenever the snippet has been shut down. The existing isInitialized() guard does not cover this state — once initialized, isInitialized() keeps returning true even after FullStory('shutdown') runs (see the consent path at index.ts#L64-L67, where shutdown is fired without flipping any local state). The rejection escapes the effect and becomes an unhandled rejection at top level.
What changes do you think we should make in order to solve the problem?
Attach a .catch to the FS.getSessionURL() chain at the single call site in src/FullstoryInitHandler.tsx, swallowing the rejection and logging it at warn level via Log (@libs/Log):
FS.getSessionURL().then((url)=>{if(!url){return;}Sentry.setContext(CONST.TELEMETRY.CONTEXT_FULLSTORY,{url});}).catch((error)=>Log.warn('[FullstoryInitHandler] Failed to get FullStory session URL',{error}));
The session URL is purely best-effort Sentry context; the existing if (!url) return; already treats a missing URL as fine. Catching at this call site keeps the error-handling decision local to the consumer that has opted into "URL is optional", preserves the rejection signal for any future caller that does care, and resolves the crash for every shutdown scenario in the report (ad-blocker, opt-out, manual shutdown).
Scope: only the FullStory session-URL lookup used for Sentry context in FullstoryInitHandler. No changes to FullStory initialization, consent/identity, native FullStory, or Sentry init.
What alternative solutions did you explore? (Optional)
Catch inside getSessionURL/getSessionId in src/libs/Fullstory/index.ts — rejected. Grep confirms getSessionURL has exactly one consumer (FullstoryInitHandler) and getSessionId has zero, so wrapping the library adds no current coverage while silently hiding the rejection from any future caller that may legitimately want to react to it. The call-site catch is sufficient and keeps the library a thin pass-through.
Track a local hasShutdown flag alongside FullStory(SHUTDOWN) at index.ts:66 and short-circuit getSessionURL — rejected. External shutdowns (ad-blocker killing fs.js, DNT, user devtools, snippet self-shutdown) bypass our code path entirely, so the .catch is still required; the flag would be dead defense.
Extend isInitialized() guard — @fullstory/browser's isInitialized() does not expose a post-shutdown predicate, so we cannot synchronously detect this state. The catch is the correct guard.
Proposal updated by depth-review — Posted proposal's change #2 (defensive catch inside getSessionId/getSessionURL at the library boundary) is over-scope and hides failures from any future caller — competitors KJ21 and wildan-m both explicitly reject it, and grep confirms FullstoryInitHandler is the only consumer of getSessionURL with zero consumers for getSessionId, so the call-site catch alone fully resolves the bug.
@mananjadhav The PR fixing this issue has been merged! The following checklist (instructions) will need to be completed before the issue can be closed. Please copy/paste the BugZero Checklist from here into a new comment on this GH and complete it. If you have the K2 extension, you can simply click: [this button]
Doesn't look like we need a checklist here. @mananjadhav in the future can you please create one and just be N/A or 'not needed'? Otherwise I have to dig around a bit to find/learn more. Thx
Activity
MelvinBot commentedon May 21, 2026
Proposal
What is the root cause of that problem?
FullstoryInitHandler.tsx:18callsFS.getSessionURL().then(...)without a.catch(). When FullStory is in a shutdown state (ad-blocker, DNT opt-out, or explicitFS('shutdown')call), the underlyingFullStory('getSessionAsync')rejects with"Shutdown called". The unhandled rejection bubbles up through React's passive effect commit phase and crashes the app.Notably,
isInitialized()still returnstrueafter shutdown, sogetSessionURLproceeds to call into a dead FullStory instance instead of bailing out.What changes do you think we should make in order to solve the problem?
Add a
.catch()to thegetSessionURL()promise chain inFullstoryInitHandler.tsx:18-24to gracefully handle the rejection. The session URL is purely informational (sets Sentry context) — failure is always safe to swallow with a warning log.What alternative solutions did you explore? (Optional)
An alternative would be tracking shutdown state inside
src/libs/Fullstory/index.ts(e.g., ahasShutdownflag set alongside theFS('shutdown')call at line 66) and returning early fromgetSessionURLwhen set. However, the.catch()approach is simpler, covers any rejection reason (not just shutdown), and matches the fact that this call is best-effort.Supporting evidence
useEffectinFullstoryInitHandler(lines 16-24) has no error handling on the promise chain.FS.init()on web is a no-op, so calling it beforegetSessionURLprovides no guard.consentAndIdentifycallsFS('shutdown')at line 66 whenshouldInitializereturns false, butisInitialized()from@fullstory/browserremainstrueafter shutdown.Next Steps for Contributor+ team: Reply with
@MelvinBot implement thisto create a draft PR,@MelvinBot <your feedback>to refine this analysis, or explain why you are rejecting Melvin's proposal.KJ21-ENG commentedon May 21, 2026
Proposal
Please re-state the problem that we are trying to solve in this issue.
On web, the app can crash while
FullstoryInitHandleris mounting if the FullStory browser snippet rejectsgetSessionAsyncwithError: Shutdown called.What is the root cause of that problem?
src/FullstoryInitHandler.tsxcallsFS.getSessionURL().then(...)inside theuseEffectthat runs when user metadata changes, but it never handles a rejected promise.On web,
FS.getSessionURL()is a thin wrapper aroundFullStory('getSessionAsync', {format: 'url'}). If the FullStory snippet is already initialized but in a shutdown state, for example afterFullStory('shutdown'), an opt-out/ad-blocker path, or another snippet shutdown condition, that async FullStory call rejects instead of returning a URL. Because the effect chain has no.catch, the rejection becomes an unhandled promise rejection during React's passive effect mount, and the web app surfaces it as a top-level crash.What changes do you think we should make in order to solve the problem?
Handle the recoverable FullStory lookup failure in
FullstoryInitHandler.Concretely, import
Loginsrc/FullstoryInitHandler.tsxand add a.catchafter the existingFS.getSessionURL().then(...)chain:This keeps the useful Sentry context when FullStory returns a URL, but treats a missing/shutdown FullStory session as non-fatal. We do not need to block rendering or crash the app just because telemetry context cannot be attached.
Scope boundaries: this should only change the FullStory session URL lookup used for Sentry context in
FullstoryInitHandler. It should not change FullStory initialization, consent/identity behavior, Sentry initialization, or native FullStory behavior.What alternative solutions did you explore? (Optional)
Catching inside
src/libs/Fullstory/index.tswould also prevent this specific rejection, but it would hide failures from every caller ofgetSessionURL(). Handling it inFullstoryInitHandleris better scoped because this caller explicitly treats the URL as optional context. CheckingisInitialized()is already done in the wrapper, so the missing piece is rejection handling for the initialized-but-shutdown state.melvin-bot commentedon May 21, 2026
Triggered auto assignment to Contributor-plus team member for initial proposal review - @mananjadhav (
External)wildan-m commentedon May 21, 2026
Proposal
Please re-state the problem that we are trying to solve in this issue.
On web, the app can crash with the "Uh-oh, something went wrong!" error overlay because of an unhandled promise rejection (
Error: Shutdown called) originating fromFullstoryInitHandler. This is a high-volume Sentry crash affecting thousands of users whenever the FullStory session is in a shutdown state (ad-blocker that killsfs.js, GDPR / Do-Not-Track opt-out, or a priorFS('shutdown')call).What is the root cause of that problem?
FullstoryInitHandlercallsFS.getSessionURL()inside auseEffectand attaches only a.then()handler — there is no.catch():App/src/FullstoryInitHandler.tsx
Lines 16 to 24 in 54e5655
getSessionURL()resolves by delegating to the FullStory browser SDK. It only short-circuits whenisInitialized()is false; once the snippet has initialized it callsFullStory('getSessionAsync', {format: 'url'}):App/src/libs/Fullstory/index.ts
Lines 98 to 103 in 54e5655
The problem is that
isInitialized()still returnstruefor a session that has initialized and then been shut down. In that stategetSessionAsyncrejects withError: Shutdown called. Because the.then()chain inFullstoryInitHandlerhas no rejection handler, the rejection escapes during React's passive-effect mount commit phase and is surfaced as a top-level unhandled rejection, which trips the global error boundary and crashes the web app.This is an expected, recoverable condition: when the FullStory session is shut down we simply have no session URL to attach to the Sentry context, and the app should continue normally.
What changes do you think we should make in order to solve the problem?
Attach a rejection handler to the
FS.getSessionURL()promise chain inFullstoryInitHandlerso a shutdown (or any other) rejection is caught instead of bubbling up as an unhandled rejection.Concretely, in
src/FullstoryInitHandler.tsxadd a.catch()to the existingFS.getSessionURL().then(...)chain at the line referenced above. The catch handler should swallow the rejection and log it at warn level (e.g. viaLog.warnfromsrc/libs/Log) so the failure is still observable in telemetry without crashing the app. When the session URL is unavailable we already skip setting the Sentry context (the existingif (!url) return;guard), so no Sentry context is set in the shutdown case — which is the correct behavior since there is no URL to record.This keeps the fix scoped to the single component that owns the unguarded promise, leaves the FullStory wrapper's existing
isInitialized()short-circuit untouched, and resolves the crash for every shutdown scenario listed in the report (ad-blocker, opt-out, manual shutdown).What alternative solutions did you explore? (Optional)
Adding the
try/catch(or.catch()) insidegetSessionURL()insrc/libs/Fullstory/index.tswas considered, but that wrapper is intentionally a thin pass-through to the SDK and other callers may want to observe the rejection; catching at theFullstoryInitHandlercall site keeps the error-handling decision local to the consumer that doesn't need the URL.[-][Sentry: APP-6K1] FullstoryInitHandler unhandled rejection ("Shutdown called") crashes web app[/-][+][$250] [Sentry: APP-6K1] FullstoryInitHandler unhandled rejection ("Shutdown called") crashes web app[/+]melvin-bot commentedon May 21, 2026
Job added to Upwork: https://www.upwork.com/jobs/~022057427299634251694
aswin-s commentedon May 21, 2026
🚨 Edited by proposal-police: This proposal was edited at 2026-05-21 11:47:25 UTC.
Proposal
Please re-state the problem that we are trying to solve in this issue.
On web, when the FullStory snippet enters a shutdown state (ad-blocker, GDPR/Do-Not-Track opt-out, or a prior
FS('shutdown')call),FullstoryInitHandlertriggers an unhandled promise rejection (Error: Shutdown called) that React surfaces during the passive-effect commit phase as a top-level crash with the "Uh-oh, something went wrong!" overlay (Sentry APP-6K1: 9,396 users / 17,283 events). Shutdown is an expected, recoverable condition for these users — the app should silently skip setting the Sentry context and keep running.What is the root cause of that problem?
In
src/FullstoryInitHandler.tsx#L16-L24theuseEffectcalls:with no
.catch. The web implementation atsrc/libs/Fullstory/index.ts#L98-L103delegates toFullStory('getSessionAsync', {format: 'url'}), which rejects withError: Shutdown calledwhenever the snippet has been shut down. The existingisInitialized()guard does not cover this state — once initialized,isInitialized()keeps returningtrueeven afterFullStory('shutdown')runs (see the consent path atindex.ts#L64-L67, where shutdown is fired without flipping any local state). The rejection escapes the effect and becomes an unhandled rejection at top level.What changes do you think we should make in order to solve the problem?
Attach a
.catchto theFS.getSessionURL()chain at the single call site insrc/FullstoryInitHandler.tsx, swallowing the rejection and logging it at warn level viaLog(@libs/Log):The session URL is purely best-effort Sentry context; the existing
if (!url) return;already treats a missing URL as fine. Catching at this call site keeps the error-handling decision local to the consumer that has opted into "URL is optional", preserves the rejection signal for any future caller that does care, and resolves the crash for every shutdown scenario in the report (ad-blocker, opt-out, manual shutdown).Scope: only the FullStory session-URL lookup used for Sentry context in
FullstoryInitHandler. No changes to FullStory initialization, consent/identity, native FullStory, or Sentry init.What alternative solutions did you explore? (Optional)
Catch inside
getSessionURL/getSessionIdinsrc/libs/Fullstory/index.ts— rejected. Grep confirmsgetSessionURLhas exactly one consumer (FullstoryInitHandler) andgetSessionIdhas zero, so wrapping the library adds no current coverage while silently hiding the rejection from any future caller that may legitimately want to react to it. The call-site catch is sufficient and keeps the library a thin pass-through.Track a local
hasShutdownflag alongsideFullStory(SHUTDOWN)atindex.ts:66and short-circuitgetSessionURL— rejected. External shutdowns (ad-blocker killingfs.js, DNT, user devtools, snippet self-shutdown) bypass our code path entirely, so the.catchis still required; the flag would be dead defense.Extend
isInitialized()guard —@fullstory/browser'sisInitialized()does not expose a post-shutdown predicate, so we cannot synchronously detect this state. The catch is the correct guard.aswin-s commentedon May 21, 2026
Proposal updated by depth-review — Posted proposal's change #2 (defensive catch inside getSessionId/getSessionURL at the library boundary) is over-scope and hides failures from any future caller — competitors KJ21 and wildan-m both explicitly reject it, and grep confirms FullstoryInitHandler is the only consumer of getSessionURL with zero consumers for getSessionId, so the call-site catch alone fully resolves the bug.
24 remaining items
melvin-bot commentedon May 25, 2026
@mananjadhav The PR fixing this issue has been merged! The following checklist (instructions) will need to be completed before the issue can be closed. Please copy/paste the BugZero Checklist from here into a new comment on this GH and complete it. If you have the K2 extension, you can simply click: [this button]
melvin-bot commentedon May 29, 2026
@mananjadhav Whoops! This issue is 2 days overdue. Let's get this updated quick!
melvin-bot commentedon May 31, 2026
Triggered auto assignment to @mallenexpensify (
Awaiting Payment)melvin-bot commentedon May 31, 2026
Payment Summary
Upwork Job
BugZero Checklist (@mallenexpensify)
mallenexpensify commentedon Jun 1, 2026
Payment Summary
Contributor+: @mallenexpensify due $250 via NewDot
Doesn't look like we need a checklist here. @mananjadhav in the future can you please create one and just be N/A or 'not needed'? Otherwise I have to dig around a bit to find/learn more. Thx
mananjadhav commentedon Jun 6, 2026
Okay noted. I generally post a comment it's not needed. But I was out few days so I couldn't post.
garrettmknight commentedon Jun 10, 2026
$250 approved for @mananjadhav