grafana/grafana · error · Error
Parent of "${path}" is not a RowsLayoutManager
Error message
Parent of "${path}" is not a RowsLayoutManager What it means
Thrown by REMOVE_ROW when resolveParentPath returns a parent that is not a RowsLayoutManager. A valid row's parent must be a RowsLayoutManager; otherwise the row's index cannot be spliced from state.rows. This guard catches scene-graph inconsistency or a path that traversed a non-rows intermediate layout.
Source
Thrown at public/app/features/dashboard-scene/mutation-api/commands/removeRow.ts:48
const { scene } = context;
enterEditModeIfNeeded(scene);
try {
const { path, moveContentTo } = payload;
if (moveContentTo === path) {
throw new Error(`moveContentTo cannot be the same path as the row being removed`);
}
// Resolve the row to remove
const resolved = resolveLayoutPath(scene.state.body, path);
if (!(resolved.item instanceof RowItem)) {
throw new Error(`Path "${path}" does not point to a row`);
}
const { parent, segment } = resolveParentPath(scene.state.body, path);
if (!(parent instanceof RowsLayoutManager)) {
throw new Error(`Parent of "${path}" is not a RowsLayoutManager`);
}
if (moveContentTo) {
const panels = resolved.item.state.layout.getVizPanels();
if (panels.length > 0) {
const targetResolved = resolveLayoutPath(scene.state.body, moveContentTo);
movePanelsToLayout(panels, targetResolved.layoutManager);
}
}
// Remove the row
const currentRows = [...parent.state.rows];
currentRows.splice(segment.index, 1);
parent.setState({ rows: currentRows });
return {
success: true,
data: { path },View on GitHub (pinned to ae3104e369)
Solutions
- Re-fetch the layout with GET_LAYOUT to confirm the parent is still a RowsLayoutManager.
- If the parent changed, the row may no longer exist in that form; move its panels individually with MOVE_PANEL before cleaning up.
- Retry on a refreshed, consistent layout snapshot.
Example fix
// before: parent was converted to a grid since GET_LAYOUT
mutationApi.execute('REMOVE_ROW', { path: '/rows/0' });
// after: re-fetch; if rows are gone, the panels are already at root
const layout = await mutationApi.execute('GET_LAYOUT', {});
// no row to remove — layout already changed Defensive patterns
Strategy: validation
Validate before calling
// Re-fetch layout to confirm parent is RowsLayoutManager before REMOVE_ROW
const layout = await mutationApi.execute('GET_LAYOUT', {});
const node = findNodeByPath(layout.data.layout, path);
if (!node || node.parentType !== 'RowsLayout') {
throw new Error('Parent is no longer a RowsLayoutManager; aborting.');
} Type guard
null
Try / catch
const res = await mutationApi.execute('REMOVE_ROW', { path, moveContentTo });
if (!res.success && res.error.includes('not a RowsLayoutManager')) {
// concurrent conversion; re-fetch and move panels individually
} Prevention
- Do not interleave UPDATE_LAYOUT with REMOVE_ROW.
- Re-fetch GET_LAYOUT on layout-type errors.
- Serialize structural mutations.
When it happens
Trigger: The path resolves to a RowItem but its computed parent is not a RowsLayoutManager, typically because a concurrent layout conversion changed the container type.
Common situations: Concurrent edits converted the parent from RowsLayout to TabsLayout or GridLayout between GET_LAYOUT and REMOVE_ROW; race condition in batched mutations.
Related errors
- Source parent of "${path}" is not a RowsLayoutManager
- Source parent of "${path}" is not a TabsLayoutManager
- moveContentTo cannot be the same path as the row being remov
- Path "${path}" does not point to a row
- Parent of "${path}" is not a TabsLayoutManager
AI-assisted analysis of grafana/grafana@ae3104e369 (2026-08-12).
Data as JSON: /api/errors/aea41cc1509b24b6.
Report an issue: GitHub.