Skip to content

Auto-recover from the known React DOM removeChild/insertBefore race instead of showing the crash page - #1421

Merged
harshithmohan merged 1 commit into
masterfrom
fix/removechild-dom-race-recovery
Aug 3, 2026
Merged

Auto-recover from the known React DOM removeChild/insertBefore race instead of showing the crash page#1421
harshithmohan merged 1 commit into
masterfrom
fix/removechild-dom-race-recovery

Conversation

@hidden4003

Copy link
Copy Markdown
Member

Summary

Sentry has been reporting an intermittent crash (issues SHOKO-WEBUI-195 and SHOKO-WEBUI-161):

NotFoundError: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.

This is a documented React limitation, not specific to React 19 or any particular version: React doesn't support something outside its control (a browser extension, a third-party script) mutating a DOM node it manages. See the official docs ("Avoid changing DOM nodes managed by React") and facebook/react#17256 (still open — a Chrome extension causes this exact error). A later commit's removeChild/insertBefore then targets a node that's already gone. It is not caused by application code.

Investigation

Two independent crash sites were confirmed in Sentry, and neither points to a shared component we can patch:

  • /webui/firstrun/start-server (most frequent) — StartServer.tsx polls server status every 500ms while the UI re-renders inside a TransitionDiv (Headless UI <Transition>).
  • /webui/collection/filter/live (rarer) — CollectionTitle.tsx, plain conditional JSX with no Headless UI, no react-virtual, no react-modal — nothing exotic.

Several targeted hypotheses were investigated and ruled out with evidence before landing on this fix:

  • A redirect race in FirstRunPage was ruled out — isPersistent is set true before polling starts, so the redirect guard can't fire while the crash is observed (confirmed via Sentry session replay).
  • A Headless UI Transition-specific bug was ruled out — the second crash site doesn't use Transition (or any DOM-managing library) at all.

The only thing the two sites share is frequent state updates landing near a React commit — the generic signature of this class of bug, which can surface anywhere and isn't fixable by chasing individual components.

Fix

SentryErrorBoundaryWrapper.tsx wraps the whole webui route tree in a single Sentry.ErrorBoundary. Previously, any uncaught error there rendered the disruptive "You Broke The Web UI!" page. This PR adds a fallback that recognizes this specific error signature (error.name === 'NotFoundError' + message matching removeChild/insertBefore) and silently calls resetError() instead, since the underlying app state is intact and only a single DOM commit glitched. A throttle guard (5s) prevents an infinite recovery loop if the error turns out to be persistent rather than transient. Sentry still captures the error for telemetry — only the user-facing crash page is suppressed.

Any other error still falls through to the existing crash page, unchanged.

Verification

  • pnpm lint and tsc --noEmit pass clean.
  • The recoverable-error predicate was checked against realistic removeChild/insertBefore exceptions plus near-miss cases (unrelated errors, other NotFoundErrors, non-Error throws) to confirm it only catches what it should.
  • Not reproducible locally (likely requires a specific browser extension or third-party script), so the fix targets the documented failure mode rather than a local repro.

@harshithmohan
harshithmohan merged commit a176b3a into master Aug 3, 2026
3 checks passed
@harshithmohan
harshithmohan deleted the fix/removechild-dom-race-recovery branch August 3, 2026 13:28
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