Repository navigation
[Due for payment 2026-07-21] [$250] [Sentry: APP-7MV] Web WebGL context creation failure on /home #93834
Description
Activity
- addedExternalAdded to denote the issue can be worked on by a contributorAdded to denote the issue can be worked on by a contributorDailyKSv2KSv2Help WantedApply this label when an issue is open to proposals by contributorsApply this label when an issue is open to proposals by contributorsBugSomething is broken. Auto assigns a BugZero manager.Something is broken. Auto assigns a BugZero manager.
on Jun 17, 2026 - added a parent issue
on Jun 17, 2026 Triggered auto assignment to Contributor-plus team member for initial proposal review - @truph01 (
External)Job added to Upwork: https://www.upwork.com/jobs/~022067265983494452435
godzillaxbox19982014-lgtm commented
on Jun 17, 2026 More actionsProposal
Please re-state the problem that we are trying to solve in this issue.
[$250] [Sentry: APP-7MV] Web WebGL context creation failure on /home
What is the root cause of that problem?
I traced this to the path that handles
APP, 7MV, WebGL:- src/libs/telemetry/trackExpenseCreationError.ts:84 (
warn):Log.warn('[trackExpenseCreationError] Failed to track expense creation error', {sentryError, originalContext: context}); - src/libs/telemetry/trackExpenseCreationError.ts:136 (
warn):Log.warn('[trackExpenseApiError] Failed to track expense API error', {sentryError, originalContext: context}); - src/libs/telemetry/trackExpenseCreationError.ts:43 (
trackExpenseCreationError):function trackExpenseCreationError(error: Error | null, context: ExpenseCreationErrorContext): void { - The local evidence matches this recurring failure shape: native callback assumes current policy/report still exists. Here,
src/libs/telemetry/trackExpenseCreationError.ts:84andsrc/libs/telemetry/trackExpenseCreationError.ts:136do not agree on the sameAPP, 7MV, WebGLvalue, so the user-visible path can show a missing or stale state even though nearby data already has the needed context.
What changes do you think we should make in order to solve the problem?
- Update
src/libs/telemetry/trackExpenseCreationError.ts:84to deriveAPP, 7MV, WebGLfrom the matched app state path that owns this flow before the value is consumed. - Carry the same normalized value through
src/libs/telemetry/trackExpenseCreationError.ts:136; that follows the safer family move to add the guard at the ownership boundary, not only at render, so the write/optimistic/read paths agree instead of only changing the visible component. - Keep the change inside the Sentry Crash, Null/Undefined, Native Hybrid surface and avoid changing do not hide crashes with blanket catch, native platform differences.
What specific scenarios should we cover in automated tests to prevent reintroducing this issue in the future?
- Add or update a regression in
tests/unit/**/*.tsfor the exact issue flow: [$250] [Sentry: APP-7MV] Web WebGL context creation failure on /home. - Cover the counter-case near
src/libs/telemetry/trackExpenseCreationError.ts:84so valid existingAPP, 7MV, WebGLbehavior still works. - Manually repeat the issue steps after the patch and confirm the visible state/action is no longer stale.
What alternative solutions did you explore? (Optional)
- I would not add a broad new condition outside these referenced paths; the safer fix is to keep
APP, 7MV, WebGLconsistent at the first read/write boundary shown above.
- src/libs/telemetry/trackExpenseCreationError.ts:84 (
Proposal
🤖 Automated analysis — confidence: High for the root cause, Medium for the exact fix (the failure is environmental, so the goal is graceful degradation, not "making WebGL work").
What is the root cause of that problem?
/homerendersSpendOverTimeSection, which mounts a Skia‑backed bar chart. On web, every@shopify/react-native-skia<Canvas>(viavictory-native'sCartesianChart) creates a GPU surface with CanvasKit'sMakeWebGLCanvasSurface. When the browser can't hand out a WebGL context — GPU blocklisted, hardware acceleration off, too many live contexts, or memory pressure — CanvasKit throwsfailed to create webgl context: err 0and does not fall back to software rendering. The throw is unhandled, so it lands in Sentry (captured from asetTimeout/rAF tick, henceauto.browser.browserapierrors.setTimeout).Evidence chain
- The exact string lives only in
canvaskit-wasm—node_modules/canvaskit-wasm/bin/full/canvaskit.js.MakeWebGLCanvasSurfacedoesn=this.GetWebGLContext(E,v); if(!n||0>n) throw "failed to create webgl context: err "+n;. The software fallback (MakeSWCanvasSurface) only runs ifMakeOnScreenGLSurfacereturns null — not whenGetWebGLContextreturns 0, so a missing context throws instead of degrading. - react-native-skia's web view calls it without catching:
@shopify/react-native-skia/src/views/SkiaPictureView.web.tsx:62(WebGLRenderer.onResize), then re-throws at line 68. /homemounts the chart:src/pages/home/HomePage.tsx:76→SpendOverTimeSectionContent.tsx→BarChart/index.tsx→BarChartContent.tsx(CartesianChartfromvictory-native).- There is no
ErrorBoundaryand no WebGL‑availability check around the chart, so the library exception propagates uncaught.
This is purely a web‑GPU/environment limitation; the chart is the first WebGL surface on
/home, which is why this route is over‑represented. The existinggodzillaxbox19982014-lgtmproposal pointing attrackExpenseCreationError.tsis unrelated — that file has nothing to do with WebGL or charts.What changes do you think we should make in order to solve the problem?
We can't make WebGL work on a machine that refuses it, but we can stop the uncaught error and degrade gracefully. Recommended (in order of preference):
- Guard with a WebGL pre‑check before mounting the chart. In
BarChart/index.tsx(web), probedocument.createElement('canvas').getContext('webgl2')once; if unavailable, render the existingfallback/empty state instead of the Skia chart. Cheap and avoids ever calling into CanvasKit. - Wrap the chart in an
ErrorBoundary(src/components/ErrorBoundary) with a graceful fallback, so any surface‑creation throw shows a non‑broken UI rather than bubbling to Sentry. This also covers context‑loss after mount (MakeWebGLCanvasSurfaceon resize atSkiaPictureView.web.tsx:62).
Do both 1 and 2 for full coverage (pre‑mount + post‑mount). A
patch-packagepatch making the library fall back toMakeSWCanvasSurfaceis also viable but is harder to maintain across upgrades.What alternative solutions did you explore? (Optional)
- Patching
canvaskit-wasm/react-native-skiato fall back to software rendering — works but adds a maintenance burden and software rendering may be slow; an error boundary is simpler and library‑version‑agnostic. - Suppressing the Sentry event only — hides the symptom but leaves users with a silently missing chart; not recommended.
Next Steps for Contributor+ team:
To accept:@MelvinBot implement [this](https://github.com/Expensify/App/issues/93834)to create a draft PR.
To refine:@MelvinBot <your feedback>
To reject: Explain why you are rejecting Melvin's proposal.- The exact string lives only in
Proposal
What is the root cause of that problem?
The
/homeroute mountsSpendOverTimeSectionas part of the Home page layout ([HomePage.tsx](https://github.com/Expensify/App/blob/18d0e4e0ba5928991588296714fd9f00a8425d07/src/pages/home/[HomePage.tsx](https://github.com/Expensify/App/blob/18d0e4e0ba5928991588296714fd9f00a8425d07/src/pages/home/HomePage.tsx#L85)#L75), HomePage.tsx). When that section is loading or ready, it rendersSearchChartView([SpendOverTimeSectionContent.tsx]()), which dispatches to one of the shared Skia-backed chart components ([SearchChartView.tsx](https://github.com/Expensify/App/blob/18d0e4e0ba5928991588296714fd9f00a8425d07/src/components/Search/[SearchChartView.tsx](https://github.com/Expensify/App/blob/18d0e4e0ba5928991588296714fd9f00a8425d07/src/components/Search/SearchChartView.tsx#L83)#L37), SearchChartView.tsx).{(state === SPEND_OVER_TIME_STATE.LOADING || state === SPEND_OVER_TIME_STATE.READY) && ( On web, the chart entry points load Skia/CanvasKit through
WithSkiaWeb, but they only provide a Suspense loading fallback and do not provide a local error boundary ([BarChart/index.tsx](), [LineChart/index.tsx](<WithSkiaWeb ), [PieChart/index.tsx](<WithSkiaWeb )). If CanvasKit cannot create a WebGL context, the lazy load/render error bubbles to the app-level error boundary, which captures the crash for<WithSkiaWeb /home.What changes do you think we should make in order to solve the problem?
Add a shared
SkiaWebLoaderwrapper for web Skia chart entry points. It should preserve the existing loading fallback, but wrapWithSkiaWebin a localErrorBoundarythat handles Skia/CanvasKit/WebGL initialization failures with an inline chart fallback instead of allowing the optional chart widget to crash the entire page.Required changes:
- Add
src/components/Charts/SkiaWebLoader.tsx. - Move the repeated
WithSkiaWebloading UI into that shared loader. - Add a Skia-specific fallback that catches WebGL/CanvasKit/Skia errors and renders an inline chart error state.
- Re-throw non-Skia errors so unrelated chart bugs still reach the normal app error boundary.
- Replace direct
WithSkiaWebusage inBarChart,LineChart,PieChart, andVictoryChartRendererwithSkiaWebLoader.
Tests that should be added:
- Unit test
SkiaWebLoaderwith a mockedWithSkiaWebthrowingfailed to create webgl context: err 0, and verify the chart error fallback renders. - Unit test that a non-Skia error is re-thrown instead of being swallowed.
- Existing Home page rendering coverage can stay unchanged unless a Home integration test is added for
SpendOverTimeSectionwith a mocked chart failure.
The existing points at
src/libs/telemetry/trackExpenseCreationError.ts, but that file is unrelated to/homechart rendering and does not participate in the Skia/CanvasKit WebGL initialization path. Changing expense telemetry would not stop the WebGL context error from bubbling out of the Home chart tree.I also considered adding a guard only inside
SpendOverTimeSection, but that would leave the same WebGL crash possible in Search chart views and HTML-rendered Victory charts. The shared loader is the minimal root-cause fix because all current web Skia chart entry points use the same dependency boundary.What alternative solutions did you explore? (Optional)
- Add
yusufdeveloper2903 commented
on Jun 17, 2026 ContributorMore actionsProposal
What is the root cause of that problem?
The exact string
failed to create webgl context: err 0is not thrown bymapbox-gl. It exists only incanvaskit-wasm(the Skia WebAssembly backend), insideMakeWebGLCanvasSurface:// node_modules/canvaskit-wasm/bin/full/canvaskit.js n = this.GetWebGLContext(E, v); if (!n || 0 > n) throw "failed to create webgl context: err " + n;
mapbox-gl@2.15.0does something different on context failure — it firesnew ErrorEvent(new Error('Failed to initialize WebGL'))(a different message, and an event, not a throw). So the error is a Skia/CanvasKit failure, not a map failure.CanvasKit is mounted on web by
@shopify/react-native-skia. On/home, the chart inSpendOverTimeSectionis rendered directly:App/src/pages/home/HomePage.tsx
Line 76 in 968eccd
<SpendOverTimeSection /> SpendOverTimeSection→BarChart, which loads CanvasKit viaWithSkiaWeband then mounts avictory-nativechart backed by a Skia<Canvas>:App/src/components/Charts/BarChart/index.tsx
Lines 14 to 28 in 968eccd
return ( <WithSkiaWeb opts={{locateFile: (file: string) => `/${file}`}} getComponent={getBarChartContent} componentProps={props} fallback={ <View style={styles.chartWebFallback}> <ActivityIndicator size="large" reasonAttributes={reasonAttributes} /> </View> } /> ); On web, Skia's
SkiaPictureView.webcreates the GPU surface inWebGLRenderer.onResizeby callingCanvasKit.MakeWebGLCanvasSurface(canvas). When the browser refuses a WebGL context — GPU blocklisted, hardware acceleration off, the per‑page live‑context cap reached, or memory pressure —GetWebGLContextreturns0and CanvasKit throws the raw string above. Crucially,onResizeis invoked from the component'sonLayouthandler and therequestAnimationFrameredrawtick, not from React's render phase. That is why it is reported with mechanismauto.browser.browserapierrors.setTimeout(Sentry wraps timers/rAF) and why it is uncaught.So:
/homemounts a Skia chart → Skia callsMakeWebGLCanvasSurfaceinside an asynconLayout/rAF tick → browser cannot allocate a WebGL context → CanvasKit throwsfailed to create webgl context: err 0from an async callback → no React boundary can catch it → it lands in Sentry.Two consequences worth noting:
-
An
ErrorBoundarydoes not fix this. React error boundaries only catch errors during render/lifecycle, not errors thrown from asetTimeout/requestAnimationFrame/onLayoutcallback. TheMapViewalready wraps its renderer in anErrorBoundaryand the same class of async GL error would still escape it:App/src/components/MapView/MapView.web.tsx
Lines 34 to 44 in 968eccd
return ( <ErrorBoundary resetKeys={[errorResetKey]} fallback={ <PendingMapView title={isOffline ? translate('distance.mapPending.title') : translate('distance.mapPending.errorTitle')} subtitle={isOffline ? translate('distance.mapPending.subtitle') : translate('distance.mapPending.errorSubtitle')} style={styles.mapEditView} /> } > -
BarChartis not the only entry point. Every Skia‑on‑web surface goes throughWithSkiaWeb, and there are four identical mount points — fixing onlyBarChartleaves the other three throwing the same way:BarChart—App/src/components/Charts/BarChart/index.tsx
Lines 14 to 28 in 968eccd
return ( <WithSkiaWeb opts={{locateFile: (file: string) => `/${file}`}} getComponent={getBarChartContent} componentProps={props} fallback={ <View style={styles.chartWebFallback}> <ActivityIndicator size="large" reasonAttributes={reasonAttributes} /> </View> } /> ); LineChart—App/src/components/Charts/LineChart/index.tsx
Lines 15 to 28 in 968eccd
<WithSkiaWeb opts={{locateFile: (file: string) => `/${file}`}} getComponent={getLineChartContent} componentProps={props} fallback={ <View style={styles.chartWebFallback}> <ActivityIndicator size="large" reasonAttributes={reasonAttributes} /> </View> } /> ); PieChart—App/src/components/Charts/PieChart/index.tsx
Lines 16 to 29 in 968eccd
<WithSkiaWeb opts={{locateFile: (file: string) => `/${file}`}} getComponent={getPieChartContent} componentProps={props} fallback={ <View style={styles.chartWebFallback}> <ActivityIndicator size="large" reasonAttributes={reasonAttributes} /> </View> } /> ); - chat
VictoryChartRenderer(charts inside report HTML) —App/src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/index.tsx
Lines 16 to 29 in 968eccd
return ( <WithSkiaWeb opts={{locateFile: (file: string) => `/${file}`}} getComponent={getBaseVictoryChartRenderer} componentProps={props} fallback={ <View style={styles.chartWebFallback}> <ActivityIndicator size="large" reasonAttributes={reasonAttributes} /> </View> } />
This is web‑only by construction: the native variant renders
BaseVictoryChartRendererdirectly with noWithSkiaWeb/WebGL, matching Sentry'sPlatform: web.
What changes do you think we should make in order to solve the problem?
Fail closed at the shared
WithSkiaWebconvergence point: probe WebGL support before mounting Skia, and when it is unavailable render the fallback that each of these call sites already passes, instead of letting CanvasKit throw asynchronously.Add a tiny web‑only helper, e.g.
isWebGLAvailable(), that memoizes a single probe:// returns false on GPU blocklist / hardware accel off / context-cap reached const canvas = document.createElement('canvas'); return !!(canvas.getContext('webgl2') ?? canvas.getContext('webgl'));
This mirrors the context types CanvasKit's
GetWebGLContextrequests (webgl2, then webgl), so atrueresult meansMakeWebGLCanvasSurfacewill succeed. Then in each of the four web mount points, when!isWebGLAvailable()render the existing fallbackViewinstead ofWithSkiaWeb. The fallback is already in scope in every one of these files — the samestyles.chartWebFallbackblock they already pass toWithSkiaWeb:App/src/components/Charts/BarChart/index.tsx
Lines 19 to 26 in 968eccd
fallback={ <View style={styles.chartWebFallback}> <ActivityIndicator size="large" reasonAttributes={reasonAttributes} /> </View> } This reuses the project's established "fail closed, render a fallback instead of crashing the report" pattern already present for malformed chart HTML:
Lines 16 to 22 in 968eccd
try { processedResult = processVictoryChartTree(tnode, fonts.typefaces.EXP_NEUE, null); } catch (error) { // Malformed chart HTML can make a parser throw. Fail closed (render nothing) instead of crashing the whole report. Log.warn('[VictoryChartRenderer] Failed to process chart tree from malformed HTML', {error}); return null; } Because the guard sits at
WithSkiaWeb(the single web convergence for Skia), it coversBarChart,LineChart,PieChart, and the chatVictoryChartRendererat once, with no new copy and no behavior change on machines that do support WebGL. To avoid repeating the guard in four files, it can be factored into a thin shared web wrapper aroundWithSkiaWebthat the charts call instead; minor structuring can be discussed in the PR phase.What alternative solutions did you explore? (Optional)
Wrapping each chart in an
ErrorBoundary(as one might infer from the existingMapView.webboundary). This does not work here: the throw originates from Skia'srequestAnimationFrame/onLayoutredraw, not from render, so React never sees it — the error would still reach Sentry. Apatch-packagepatch making Skia fall back toMakeSWCanvasSurface(software rendering) is also possible, but it adds upgrade‑maintenance cost and silently degrades to slow CPU rendering; the pre‑mount WebGL probe is cheaper, library‑version‑agnostic, and keeps the already‑designed fallback UI.-
github-actions commented
on Jun 17, 2026 on Jun 17, 2026 – with GitHub ActionsContributorMore actions⚠️ @yusufdeveloper2903 Thanks for your proposal. Please update it to follow the proposal template, as proposals are only reviewed if they follow that format (note the mandatory sections).47 remaining items
- addedAwaiting PaymentAuto-added when associated PR is deployed to productionAuto-added when associated PR is deployed to production
on Jul 15, 2026 Current assignee @mallenexpensify is eligible for the Awaiting Payment assigner, not assigning anyone new.
Sentry impact update (2026-07-15, 14d window)
- Sentry: APP-7MV
- Users (14d / total): 166 / 1113
- Status: Still active on production builds 9.4.32–9.4.34
$250 approved for @truph01
- removed a parent issue
on Sep 23, 2026
Metadata
Metadata
Labels
Type
Projects
- StatusShow more project fieldsDone
Sentry
https://expensify.sentry.io/issues/APP-7MV
Impact (snapshot at filing)
/homeStack trace (top frames, first-party only)
Suspected cause
WebGL context creation fails on
/home, likely due to GPU/browser restrictions or memory pressure on the client.Reproduction
Unknown — see Sentry events linked from the Sentry issue.
Related
Upwork Automation - Do Not Edit
Issue Owner
Current Issue Owner: @mallenexpensify