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
Open Discover in xl viewport and run a metrics ES|QL query (e.g. TS metrics-* or TS remote_cluster:metrics-*)
Open the Metric Insights flyout on any metric
Duplicate the tab (flyout state is preserved in the new tab)
Close the duplicated tab
Console error:NotFoundError: Failed to execute 'insertBefore' on 'Node'
Changing the flyout from type="push" to type="overlay" eliminates the error completely. The root cause is that EuiFlyout in push mode calls setGlobalCSSVariables inside a useLayoutEffect (synchronous), which triggers a nested Emotion <Global> re-render during the same React commit where react-reverse-portal is doing replaceChild. The stale DOM references cause the crash. See the full analysis below.
Screen.Recording.2026-02-16.at.17.59.10.mov
Bug
When a push-mode EuiFlyout mounts or unmounts during a React commit that also involves other DOM mutations (portal swaps, tab switching, conditional rendering), the browser throws:
NotFoundError: Failed to execute 'insertBefore' on 'Node':
The node before which the new node is to be inserted is not a child of this node.
The stack trace points to @emotion/sheet → _insertTag → container.insertBefore(tag, before).
Changing type="push" to type="overlay" completely eliminates the error.
When it does NOT happen
Push flyouts that mount/unmount in isolation (e.g., user clicks a button, flyout opens — no other DOM work in the same commit) work fine.
Overlay-mode flyouts never trigger this, regardless of how complex the surrounding DOM operations are.
In Kibana, the Discover document viewer flyout also uses push mode but does not trigger this error, because it mounts in response to a user action without concurrent portal DOM operations.
When it DOES happen
The error requires all of these conditions simultaneously:
EuiFlyout with type="push" (and viewport >= pushMinBreakpoint so isPushed is true)
The flyout mounts or unmounts during a React commit where other components are also performing DOM mutations, for example, react-reverse-portal doing replaceChild to swap tab content
How we found this
In PR #251637, we are adding the ability to persist the Metric Insights flyout state across Discover tabs. The PR introduces useRestorableState for flyoutState, so when a user duplicates a tab that has the flyout open, the new tab restores the flyout state and the push-mode EuiFlyout is visible in the duplicated tab.
Analysis
We believe the problem is that EuiFlyout calls setGlobalCSSVariables inside a useLayoutEffect (synchronous), which is unique to push-mode flyouts. All other EUI components that call setGlobalCSSVariables use useEffect (asynchronous).
Step by step
1. Push flyout mounts or unmounts -> useLayoutEffect calls setGlobalCSSVariables
useLayoutEffect(()=>{if(!isPushed)return;// <- overlay exits here, push continuesdocument.body.style[paddingSide]=`${paddingWidth}px`;setGlobalCSSVariables({[cssVarName]: `${paddingWidth}px`});// <- TRIGGERS ON MOUNTreturn()=>{document.body.style[paddingSide]='';setGlobalCSSVariables({[cssVarName]: null});// <- TRIGGERS ON UNMOUNT};},[...]);
2. setGlobalCSSVariables forces a synchronous re-render of EuiThemeProvider
setGlobalCSSVariables calls setState on themeCSSVariables in EuiThemeProvider. Because it's called from a useLayoutEffect, React processes this state update synchronously within the current commit — it creates a nested commit.
3. The <Global> component re-renders inside the nested commit
if(!isPushed){return;// Only push-type flyouts manage body padding}
With type="overlay", isPushed is false. The useLayoutEffect returns immediately. setGlobalCSSVariables is never called. No synchronous state update, no nested commit, no <Global> re-render, no crash.
Secondary issue(Could be related?): _flyout_overlay.tsx uses standalone @emotion/css
This creates a separate Emotion cache with key: 'css' — the same key that Kibana uses for its main Emotion cache (configured in eui_provider.tsx: createCache({ key: 'css', container: meta[name="emotion"] })). Emotion explicitly warns about this:
"Please make sure it's unique (and not equal to 'css') as it's used for linking styles to your cache. If multiple caches share the same key they might 'fight' for each other's style elements."
The css() call runs on every render of EuiFlyoutOverlay, even for push-mode flyouts where the overlay mask is never rendered.
TL;DR
Same error as this issue different behavior.
In PR #251637, with the code as-is:
TS metrics-*orTS remote_cluster:metrics-*)NotFoundError: Failed to execute 'insertBefore' on 'Node'Changing the flyout from
type="push"totype="overlay"eliminates the error completely. The root cause is thatEuiFlyoutin push mode callssetGlobalCSSVariablesinside auseLayoutEffect(synchronous), which triggers a nested Emotion<Global>re-render during the same React commit wherereact-reverse-portalis doingreplaceChild. The stale DOM references cause the crash. See the full analysis below.Screen.Recording.2026-02-16.at.17.59.10.mov
Bug
When a push-mode
EuiFlyoutmounts or unmounts during a React commit that also involves other DOM mutations (portal swaps, tab switching, conditional rendering), the browser throws:The stack trace points to
@emotion/sheet→_insertTag→container.insertBefore(tag, before).Changing
type="push"totype="overlay"completely eliminates the error.When it does NOT happen
When it DOES happen
The error requires all of these conditions simultaneously:
EuiFlyoutwithtype="push"(and viewport >=pushMinBreakpointsoisPushedistrue)react-reverse-portaldoingreplaceChildto swap tab contentHow we found this
In PR #251637, we are adding the ability to persist the Metric Insights flyout state across Discover tabs. The PR introduces
useRestorableStateforflyoutState, so when a user duplicates a tab that has the flyout open, the new tab restores the flyout state and the push-modeEuiFlyoutis visible in the duplicated tab.Analysis
We believe the problem is that
EuiFlyoutcallssetGlobalCSSVariablesinside auseLayoutEffect(synchronous), which is unique to push-mode flyouts. All other EUI components that callsetGlobalCSSVariablesuseuseEffect(asynchronous).Step by step
1. Push flyout mounts or unmounts ->
useLayoutEffectcallssetGlobalCSSVariablesThe
useLayoutEffectinflyout.component.tsxruns synchronously during React's commit phase, both its body (on mount) and its cleanup (on unmount):2.
setGlobalCSSVariablesforces a synchronous re-render ofEuiThemeProvidersetGlobalCSSVariablescallssetStateonthemeCSSVariablesinEuiThemeProvider. Because it's called from auseLayoutEffect, React processes this state update synchronously within the current commit — it creates a nested commit.3. The
<Global>component re-renders inside the nested commitEuiThemeProviderconditionally renders a<Global>component whenthemeCSSVariablesis truthy:When
themeCSSVariableschanges, this<Global>re-renders. ItsuseInsertionEffectflushes old style tags and inserts new ones.Why
type="overlay"doesn't trigger thisLine 366-367 of
flyout.component.tsx:With
type="overlay",isPushedisfalse. TheuseLayoutEffectreturns immediately.setGlobalCSSVariablesis never called. No synchronous state update, no nested commit, no<Global>re-render, no crash.Secondary issue(Could be related?):
_flyout_overlay.tsxuses standalone@emotion/css_flyout_overlay.tsximports from the standalone@emotion/csspackage:This creates a separate Emotion cache with
key: 'css'— the same key that Kibana uses for its main Emotion cache (configured ineui_provider.tsx:createCache({ key: 'css', container: meta[name="emotion"] })). Emotion explicitly warns about this:The
css()call runs on every render ofEuiFlyoutOverlay, even for push-mode flyouts where the overlay mask is never rendered.