Auto-recover from the known React DOM removeChild/insertBefore race instead of showing the crash page - #1421
Merged
Conversation
…nstead of showing the crash page
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.
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/insertBeforethen 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.tsxpolls server status every 500ms while the UI re-renders inside aTransitionDiv(Headless UI<Transition>)./webui/collection/filter/live(rarer) —CollectionTitle.tsx, plain conditional JSX with no Headless UI, noreact-virtual, noreact-modal— nothing exotic.Several targeted hypotheses were investigated and ruled out with evidence before landing on this fix:
FirstRunPagewas ruled out —isPersistentis settruebefore polling starts, so the redirect guard can't fire while the crash is observed (confirmed via Sentry session replay).Transition-specific bug was ruled out — the second crash site doesn't useTransition(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.tsxwraps the wholewebuiroute tree in a singleSentry.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 matchingremoveChild/insertBefore) and silently callsresetError()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 lintandtsc --noEmitpass clean.removeChild/insertBeforeexceptions plus near-miss cases (unrelated errors, otherNotFoundErrors, non-Errorthrows) to confirm it only catches what it should.