wavetermdev/waveterm · error
Invalid InsertOperation
Error message
Invalid InsertOperation
What it means
moveNode inserts a node either under a computed parent (possibly adding an intermediate node) or under the action's parent; if neither a parent nor a root insertion path is available, the InsertOperation is structurally invalid and the function throws. This guards against inserting a node with no resolvable parent/index.
Source
Thrown at frontend/layout/lib/layoutTree.ts:270
if (oldParent.id === parent.id) {
const curIndexInParent = parent.children!.indexOf(node);
if (curIndexInParent >= action.index) {
startingIndex = action.index + 1;
}
} else {
node.size = DefaultNodeSize;
}
}
if (!parent && action.insertAtRoot) {
if (!rootNode.children) {
addIntermediateNode(rootNode);
}
addChildAt(rootNode, action.index, node);
} else if (parent) {
addChildAt(parent, action.index, node);
} else {
throw new Error("Invalid InsertOperation");
}
// Remove nodeToInsert from its old parent
if (oldParent) {
removeChild(oldParent, node, startingIndex);
}
}
export function insertNode(layoutState: LayoutTreeState, action: LayoutTreeInsertNodeAction) {
if (!action?.node) {
console.error("insertNode cannot run, no insert node action provided");
return;
}
if (!layoutState.rootNode) {
layoutState.rootNode = action.node;
} else {
const insertLoc = findNextInsertLocation(layoutState.rootNode, DEFAULT_MAX_CHILDREN);
addChildAt(insertLoc.node, insertLoc.index, action.node);View on GitHub (pinned to a4447c1563)
Solutions
- Verify action.parentId targets an existing node at the time the reducer runs
- Recompute or drop the pending action if the referenced parent was removed
- Clear stale pendingActions on layout mutation (close/delete of nodes)
Example fix
// before
treeReducer({ type: "move", parentId: oldParentId, ... });
// after
const parent = findNode(tree, oldParentId);
if (!parent) return; // drop stale move action
treeReducer({ type: "move", parentId: oldParentId, ... }); Defensive patterns
Strategy: validation
Validate before calling
const parent = findNode(tree, action.parentId);
if (action.parentId != null && parent == null) {
return; // stale operation — parent no longer exists
} Type guard
function hasValidParent(tree: LayoutTree, action: InsertOperation): boolean {
return action.parentId == null || findNode(tree, action.parentId) != null;
} Try / catch
try {
treeReducer(state, moveAction);
} catch (e) {
if (e.message === "Invalid InsertOperation") {
state = clearPendingAction(state); // drop stale move
} else { throw e; }
} Prevention
- Clear pending move/insert actions whenever nodes are removed
- Re-validate parentId against the current tree inside the reducer before applying
- Avoid queuing layout operations across async boundaries that may delete blocks
When it happens
Trigger: treeReducer dispatches an insert/move where action.parentId refers to a node no longer in the tree and the target node is not the root — parent resolves to null and no root path applies.
Common situations: Stale move operation referencing a node deleted concurrently; corrupted layout state after removing a parent before applying a queued insert; race between block close and pending move action.
Related errors
- Invalid node
- Invalid direction: ${direction}
- unable to get layout id for given tab id %s: %w
- unable to get layout state for given id %s: %w
- unable to update layout state with new actions: %w
AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01).
Data as JSON: /api/errors/873f6efcf35a049f.
Report an issue: GitHub.