{"record":{"id":"873f6efcf35a049f","repo":"wavetermdev/waveterm","slug":"invalid-insertoperation","errorCode":null,"errorMessage":"Invalid InsertOperation","messagePattern":"Invalid InsertOperation","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"frontend/layout/lib/layoutTree.ts","lineNumber":270,"sourceCode":"        if (oldParent.id === parent.id) {\n            const curIndexInParent = parent.children!.indexOf(node);\n            if (curIndexInParent >= action.index) {\n                startingIndex = action.index + 1;\n            }\n        } else {\n            node.size = DefaultNodeSize;\n        }\n    }\n\n    if (!parent && action.insertAtRoot) {\n        if (!rootNode.children) {\n            addIntermediateNode(rootNode);\n        }\n        addChildAt(rootNode, action.index, node);\n    } else if (parent) {\n        addChildAt(parent, action.index, node);\n    } else {\n        throw new Error(\"Invalid InsertOperation\");\n    }\n\n    // Remove nodeToInsert from its old parent\n    if (oldParent) {\n        removeChild(oldParent, node, startingIndex);\n    }\n}\n\nexport function insertNode(layoutState: LayoutTreeState, action: LayoutTreeInsertNodeAction) {\n    if (!action?.node) {\n        console.error(\"insertNode cannot run, no insert node action provided\");\n        return;\n    }\n    if (!layoutState.rootNode) {\n        layoutState.rootNode = action.node;\n    } else {\n        const insertLoc = findNextInsertLocation(layoutState.rootNode, DEFAULT_MAX_CHILDREN);\n        addChildAt(insertLoc.node, insertLoc.index, action.node);","sourceCodeStart":252,"sourceCodeEnd":288,"githubUrl":"https://github.com/wavetermdev/waveterm/blob/a4447c1563b2df285ab89e76c82f91e1a1a49c1e/frontend/layout/lib/layoutTree.ts#L252-L288","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)"],"exampleFix":"// before\ntreeReducer({ type: \"move\", parentId: oldParentId, ... });\n\n// after\nconst parent = findNode(tree, oldParentId);\nif (!parent) return; // drop stale move action\ntreeReducer({ type: \"move\", parentId: oldParentId, ... });","handlingStrategy":"validation","validationCode":"const parent = findNode(tree, action.parentId);\nif (action.parentId != null && parent == null) {\n    return; // stale operation — parent no longer exists\n}","typeGuard":"function hasValidParent(tree: LayoutTree, action: InsertOperation): boolean {\n    return action.parentId == null || findNode(tree, action.parentId) != null;\n}","tryCatchPattern":"try {\n    treeReducer(state, moveAction);\n} catch (e) {\n    if (e.message === \"Invalid InsertOperation\") {\n        state = clearPendingAction(state); // drop stale move\n    } else { throw e; }\n}","preventionTips":["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"],"tags":["layout","invalid-operation","state-race"],"backgroundTag":"invalid-insert-operation","analyzedSha":"a4447c1563b2df285ab89e76c82f91e1a1a49c1e","analyzedAt":"2026-09-01T15:26:23.972Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}