Skip to content

fix(web): guard MeshGradient against missing WebGL context - #6318

Merged
saddlepaddle merged 1 commit into
mainfrom
sentry/ui-mesh-gradient-webgl-guard
Aug 13, 2026
Merged

saddlepaddle merged 1 commit into
mainfrom
sentry/ui-mesh-gradient-webgl-guard

Conversation

@Kitenite

@Kitenite Kitenite commented Aug 10, 2026 •

Copy link
Copy Markdown
Member

Problem

Unhandled TypeErrors from the shared MeshGradient component in two apps: Cannot read properties of null (reading 'uniformMatrix4fv') in the web app (/integrations, /agents) and Argument 1 ('program') ... must be an instance of WebGLProgram on the marketing homepage. Triggered on browsers that cannot provide a WebGL context (GPU blocklisted, headless, software rendering, or context-creation failure). The gradient is purely decorative; the page is otherwise fine.

Root cause

packages/ui/src/components/mesh-gradient.tsx initializes stripe-gradient unconditionally in its mount effect. When canvas.getContext("webgl"/"webgl2") returns null, the library proceeds with the null context and dereferences it, throwing from library internals.

Fix

Fix at the failure site in the shared component (no Sentry-side suppression):

  • Feature-detect WebGL on a throwaway canvas (webgl2 || webgl) before constructing the gradient; bail out and leave the static background when unavailable.
  • Wrap the initGradient call in a try/catch scoped to initialization only — on failure, run the existing teardown (pause + decoy-element disconnect) and bail. Errors after successful init are not swallowed.

Behaviour for users with working WebGL is unchanged.

Verification

bunx biome check packages/ui/src/components/mesh-gradient.tsx:

Checked 1 file in 27ms. No fixes applied.

cd packages/ui && bun run typecheck:

$ tsc --noEmit --emitDeclarationOnly false
exit=0

Refs WEB-1W
Refs MARKETING-3E

https://claude.ai/code/session_4d58b950-d21f-466c-ba7a-bb20bf855a26


Summary by cubic

Guarded the shared MeshGradient against missing WebGL to stop runtime errors and leave a static background when WebGL isn’t available. Prevents crashes on web app integrations/agents and the marketing homepage; addresses WEB-1W and MARKETING-3E.

  • Bug Fixes
    • Detect WebGL via a probe canvas; skip gradient init when unsupported.
    • Wrap initGradient in try/catch; on failure, teardown (pause + disconnect) and exit. Behavior is unchanged when WebGL works.

Written for commit 7da800b. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Bug Fixes
    • Improved mesh gradient stability by checking WebGL support before initialization.
    • Added safer handling when gradient setup fails.
    • Ensured gradients are properly paused and disconnected during cleanup.

@coderabbitai

coderabbitai Bot commented Aug 10, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

MeshGradient now checks WebGL support before initialization. It uses shared teardown logic to pause and disconnect gradients. Initialization errors trigger cleanup and stop effect setup.

Changes

Mesh gradient lifecycle

Layer / File(s) Summary
WebGL-safe initialization and cleanup
packages/ui/src/components/mesh-gradient.tsx
The component detects WebGL support, skips unsupported canvases, centralizes gradient teardown, and cleans up initialization failures. Successful initialization retains delayed noiseSpeed configuration.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: saddlepaddle

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title uses conventional commit format and clearly identifies the WebGL guard added to MeshGradient.
Description check ✅ Passed The description clearly explains the problem, root cause, fix, affected behavior, and verification results, despite omitting the template checklist.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sentry/ui-mesh-gradient-webgl-guard

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/ui/src/components/mesh-gradient.tsx`:
- Around line 80-86: Update the effect around the delayed noiseSpeed update to
store the setTimeout handle before defining teardown, then clear that handle
inside teardown before disconnecting or releasing the gradient. Preserve the
existing callback behavior while ensuring it cannot run after cleanup or a
speed-triggered effect rerun.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7f301c68-284d-46d1-ab45-170588b5e42a

📥 Commits

Reviewing files that changed from the base of the PR and between 31fb0fc and 7da800b.

📒 Files selected for processing (1)
  • packages/ui/src/components/mesh-gradient.tsx

Comment on lines +80 to +86
setTimeout(() => {
if (gradient?.uniforms?.u_global?.value?.noiseSpeed) {
gradient.uniforms.u_global.value.noiseSpeed.value = speed;
}
}, 100);

return teardown;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Cancel the delayed noiseSpeed update during teardown.

teardown does not retain the timeout handle. If the effect re-runs because speed changes, or the component unmounts before 100 ms, the callback still runs against the disconnected gradient. This creates stale work and can surface an uncaught post-cleanup error.

Store the handle before defining teardown, and clear it inside teardown.

Proposed fix
 		const gradient = new Gradient() as GradientInstance;
+		let noiseSpeedTimeout: ReturnType<typeof setTimeout> | undefined;

 		const teardown = () => {
+			if (noiseSpeedTimeout !== undefined) {
+				clearTimeout(noiseSpeedTimeout);
+			}
 			if (gradient.pause) {
 				gradient.pause();
 			}
 			// existing cleanup
 		};

-		setTimeout(() => {
+		noiseSpeedTimeout = setTimeout(() => {
 			if (gradient?.uniforms?.u_global?.value?.noiseSpeed) {
 				gradient.uniforms.u_global.value.noiseSpeed.value = speed;
 			}
 		}, 100);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/components/mesh-gradient.tsx` around lines 80 - 86, Update
the effect around the delayed noiseSpeed update to store the setTimeout handle
before defining teardown, then clear that handle inside teardown before
disconnecting or releasing the gradient. Preserve the existing callback behavior
while ensuring it cannot run after cleanup or a speed-triggered effect rerun.

@github-actions

github-actions Bot commented Aug 10, 2026 •

Copy link
Copy Markdown
Contributor

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ✅ Neon database branch

Thank you for your contribution! 🎉

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 1 file

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/ui/src/components/mesh-gradient.tsx">

<violation number="1" location="packages/ui/src/components/mesh-gradient.tsx:29">
P2: Repeated mounts or `speed` updates leak probe contexts until browsers refuse the real gradient context, leaving otherwise-supported gradients on the static fallback. Cache the capability result (or explicitly lose the probe context) so the check does not allocate a new context per effect run.</violation>

<violation number="2" location="packages/ui/src/components/mesh-gradient.tsx:34">
P3: The `supportsWebgl()` probe swallows any error from `document.createElement` or `getContext` in a silent empty catch with no rationale comment and no observability. If the failure is something other than WebGL being genuinely unavailable (e.g. an allocation/sandboxing error), it becomes indistinguishable from real lack of support and goes completely unlogged. Since the rest of this PR adds an inline rationale comment to the init catch for exactly this reason, consider adding a brief inline comment here explaining that the probe is a best-effort feature check (so an unsupported environment simply keeps the static background), keeping the fix consistent with the swallow-with-rationale convention used below.</violation>
</file>
Architecture diagram
sequenceDiagram
    participant Browser as Browser
    participant Canvas as <canvas> Element
    participant Probe as WebGL Probe (throwaway)
    participant Mesh as MeshGradient Component
    participant Gradient as stripe-gradient Library
    participant DOM as DOM (body)

    Note over Mesh: Mount effect triggered

    Mesh->>Canvas: canvasRef.current
    alt Canvas is null (edge case)
        Mesh-->>DOM: bail out early, no gradient
    else Canvas exists
        Mesh->>Probe: supportsWebgl() check
        Probe->>Probe: document.createElement("canvas")
        Probe->>Probe: probe.getContext("webgl2")
        alt WebGL2 available
            Probe-->>Mesh: true
        else WebGL2 not available
            Probe->>Probe: probe.getContext("webgl")
            alt WebGL available
                Probe-->>Mesh: true
            else WebGL not available (blocklisted/headless/software)
                Probe-->>Mesh: false
            end
        end

        alt WebGL not supported
            Mesh-->>DOM: bail out, leave static background
        else WebGL supported
            Mesh->>Gradient: new Gradient()
            Gradient-->>Mesh: gradient instance

            Mesh->>Gradient: gradient.initGradient(`#${canvasId}`)
            alt Init succeeds
                Gradient-->>Mesh: gradient initialized
                Mesh->>Mesh: setTimeout(100ms)
                Mesh->>Gradient: set noiseSpeed from props
                Note over Mesh: Normal runtime — gradient animates
                Note right of Mesh: teardown callback registered<br/>(pause + disconnect)
            else Init fails (null context despite probe)
                Note over Mesh,Gradient: NEW: try/catch guards init only
                Mesh->>DOM: teardown() — pause + disconnect gradient
                Mesh-->>DOM: return early, no crash
            end
        end
    end

    Note over Mesh: Cleanup on unmount or deps change
    Mesh->>DOM: teardown() — pause + disconnect gradient
Loading

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment on lines +29 to +36
function supportsWebgl(): boolean {
try {
const probe = document.createElement("canvas");
return Boolean(probe.getContext("webgl2") || probe.getContext("webgl"));
} catch {
return false;
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Repeated mounts or speed updates leak probe contexts until browsers refuse the real gradient context, leaving otherwise-supported gradients on the static fallback. Cache the capability result (or explicitly lose the probe context) so the check does not allocate a new context per effect run.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/ui/src/components/mesh-gradient.tsx, line 29:

<comment>Repeated mounts or `speed` updates leak probe contexts until browsers refuse the real gradient context, leaving otherwise-supported gradients on the static fallback. Cache the capability result (or explicitly lose the probe context) so the check does not allocate a new context per effect run.</comment>

<file context>
@@ -26,6 +26,15 @@ interface GradientInstance {
 	};
 }
 
+function supportsWebgl(): boolean {
+	try {
+		const probe = document.createElement("canvas");
</file context>
Suggested change
function supportsWebgl(): boolean {
try {
const probe = document.createElement("canvas");
return Boolean(probe.getContext("webgl2") || probe.getContext("webgl"));
} catch {
return false;
}
}
let webglSupport: boolean | undefined;
function supportsWebgl(): boolean {
if (webglSupport !== undefined) return webglSupport;
try {
const probe = document.createElement("canvas");
webglSupport = Boolean(
probe.getContext("webgl2") || probe.getContext("webgl"),
);
} catch {
webglSupport = false;
}
return webglSupport;
}

const probe = document.createElement("canvas");
return Boolean(probe.getContext("webgl2") || probe.getContext("webgl"));
} catch {
return false;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The supportsWebgl() probe swallows any error from document.createElement or getContext in a silent empty catch with no rationale comment and no observability. If the failure is something other than WebGL being genuinely unavailable (e.g. an allocation/sandboxing error), it becomes indistinguishable from real lack of support and goes completely unlogged. Since the rest of this PR adds an inline rationale comment to the init catch for exactly this reason, consider adding a brief inline comment here explaining that the probe is a best-effort feature check (so an unsupported environment simply keeps the static background), keeping the fix consistent with the swallow-with-rationale convention used below.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/ui/src/components/mesh-gradient.tsx, line 34:

<comment>The `supportsWebgl()` probe swallows any error from `document.createElement` or `getContext` in a silent empty catch with no rationale comment and no observability. If the failure is something other than WebGL being genuinely unavailable (e.g. an allocation/sandboxing error), it becomes indistinguishable from real lack of support and goes completely unlogged. Since the rest of this PR adds an inline rationale comment to the init catch for exactly this reason, consider adding a brief inline comment here explaining that the probe is a best-effort feature check (so an unsupported environment simply keeps the static background), keeping the fix consistent with the swallow-with-rationale convention used below.</comment>

<file context>
@@ -26,6 +26,15 @@ interface GradientInstance {
+		const probe = document.createElement("canvas");
+		return Boolean(probe.getContext("webgl2") || probe.getContext("webgl"));
+	} catch {
+		return false;
+	}
+}
</file context>

@saddlepaddle
saddlepaddle merged commit e806f7b into main Aug 13, 2026
19 checks passed
@saddlepaddle
saddlepaddle deleted the sentry/ui-mesh-gradient-webgl-guard branch August 13, 2026 18:38
saddlepaddle added a commit that referenced this pull request Aug 13, 2026
…y fix wave (#6437)

* fix(desktop,host-service): address unreviewed findings from the sentry fix wave

Four defects that automated reviewers flagged on PRs #6191, #6258, #6316
and #6318 and that were merged without anyone acting on them.

- host-service-coordinator: crash reports redacted the child's own auth
  secret but not the cloud credential the coordinator also injects into
  its env, so a logged Authorization header could reach Sentry. Replace
  the single replacement with a per-instance redaction list.
- workspaces/create: getCurrentBranch() returns null for a detached HEAD
  and for any git failure alike, so a missing repo or a permission wall
  was reported to the user as a detached HEAD and stopped being visible
  in Sentry. Confirm HEAD is genuinely detached first.
- mesh-gradient: the WebGL probe leaked a context per mount, which can
  exhaust the browser's context budget and pin the real gradient to the
  static fallback. Release the probe and cache the result.
- workspace-creation: existsSync() is true for a regular file, so a repo
  path pointing at a file passed the directory guard.

* refactor(desktop): read HEAD once via a discriminated result instead of a second git client

This branch was previously deployed

1 inactive deployment
preview — 7da800bc Deployed Aug 10, 2026 by Kitenite via Deploy Web #7370
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.

2 participants