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

  1. Re-fetch the layout with GET_LAYOUT to confirm the parent is still a RowsLayoutManager.
  2. If the parent changed, the row may no longer exist in that form; move its panels individually with MOVE_PANEL before cleaning up.
  3. 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

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


AI-assisted analysis of grafana/grafana@ae3104e369 (2026-08-12). Data as JSON: /api/errors/aea41cc1509b24b6. Report an issue: GitHub.