{"record":{"id":"e4d2e6816d1ee98e","repo":"siyuan-note/siyuan","slug":"the-top-level-notebook-document-cannot-be-removed","errorCode":null,"errorMessage":"The top-level notebook document cannot be removed or moved","messagePattern":"The top-level notebook document cannot be removed or moved","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/file.go","lineNumber":1693,"sourceCode":"\t\treturn\n\t}\n\ttoPath = normalizeBoxDocTarget(toBoxID, toPath)\n\tpathsBoxes, err := getBoxesByPathsStrict(fromPaths)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tfromPaths = util.FilterMoveDocFromPaths(fromPaths, toPath)\n\tif 1 > len(fromPaths) {\n\t\treturn\n\t}\n\n\tfromPaths = orderMoveDocPaths(fromPaths, pathsBoxes)\n\n\tfor _, fromPath := range fromPaths {\n\t\tfromBox := pathsBoxes[fromPath]\n\t\tif nil != fromBox && IsBoxDocPath(fromBox.ID, fromPath) {\n\t\t\treturn errors.New(Conf.Language(341))\n\t\t}\n\t}\n\n\tif 1 == len(fromPaths) {\n\t\t// 移动到自己的父文档下的情况相当于不移动，直接返回\n\t\tif fromBox := pathsBoxes[fromPaths[0]]; nil != fromBox && fromBox.ID == toBoxID {\n\t\t\tparentDir := path.Dir(fromPaths[0])\n\t\t\tif (\"/\" == toPath && \"/\" == parentDir) || (parentDir+\".sy\" == toPath) {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\t// 检查路径深度是否超过限制\n\tfor _, fromPath := range fromPaths {\n\t\tfromBox := pathsBoxes[fromPath]\n\t\tchildDepth := util.GetChildDocDepth(filepath.Join(util.DataDir, fromBox.ID, fromPath))\n\t\tif depth := strings.Count(toPath, \"/\") + childDepth; 6 < depth && !Conf.FileTree.AllowCreateDeeper {","sourceCodeStart":1675,"sourceCodeEnd":1711,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/file.go#L1675-L1711","documentation":"Thrown by MoveDocs when one of the source paths is the top-level notebook document itself (the root .sy file). Conf.Language(341) = 'The top-level notebook document cannot be removed or moved'. IsBoxDocPath(fromBox.ID, fromPath) returns true when fromPath matches the notebook's root document path. Moving the root would orphan the entire notebook, so it is explicitly blocked.","triggerScenarios":"Calling MoveDocs where one of fromPaths is the root document path of its notebook (e.g., '/20210101000000-abcdefgh.sy' matching the box's root). This can happen if a UI drag operation inadvertently includes the top-level document or if path construction logic produces the root path.","commonSituations":"A user selects all documents in a notebook (including the root) and drags them elsewhere. A plugin iterates paths and accidentally includes the notebook root. A path-filtering bug fails to exclude the root document from a batch move list.","solutions":["Filter out the root document path from fromPaths before calling MoveDocs — use IsBoxDocPath to exclude it.","In UI code, prevent the root document from being selected for move operations.","If the user intends to reorganize the entire notebook, suggest exporting and re-importing instead of moving the root.","Validate each path in fromPaths against IsBoxDocPath before submission."],"exampleFix":"// before\nerr := model.MoveDocs(fromPaths, toBoxID, toPath, callback)\n\n// after\nfiltered := fromPaths[:0]\nfor _, fp := range fromPaths {\n    box := conf.Box(boxIDForPath(fp))\n    if box != nil && !model.IsBoxDocPath(box.ID, fp) {\n        filtered = append(filtered, fp)\n    }\n}\nerr := model.MoveDocs(filtered, toBoxID, toPath, callback)","handlingStrategy":"validation","validationCode":"// Pre-check: filter out root document paths from the move list\nfiltered := make([]string, 0, len(fromPaths))\nfor _, fp := range fromPaths {\n    box := pathsBoxes[fp]\n    if box != nil && model.IsBoxDocPath(box.ID, fp) {\n        continue // skip root document\n    }\n    filtered = append(filtered, fp)\n}\nfromPaths = filtered","typeGuard":"func isRootDocPath(boxID, p string) bool {\n    return model.IsBoxDocPath(boxID, p)\n}","tryCatchPattern":null,"preventionTips":["Filter fromPaths using IsBoxDocPath before calling MoveDocs.","In UI code, prevent the root document from being included in drag-and-drop move operations.","To reorganize an entire notebook, use export/import instead of moving the root."],"tags":["move","notebook","root-document","validation"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}