siyuan-note/siyuan · error
Conf.Language(341)
Error message
Conf.Language(341)
What it means
MoveDocs refuses to move or remove a notebook's top-level (root) document ('The top-level notebook document cannot be removed or moved', key 341). The root .sy anchors the notebook tree and SiYuan forbids relocating or deleting it. The check runs before any mutation.
Solutions
- Exclude the root document (path equal to the top-level .sy) from fromPaths
- Move or delete the entire notebook instead if the root must go
- Skip root entries when building the source list
Example fix
// before
await fetchPost('/api/filetree/moveDocs', {fromPaths: ['/' + rootDocID + '.sy'], toNotebook: otherBox, toPath: '/'})
// after
const paths = fromPaths.filter(p => p.split('/').length > 2) // drop top-level docs
if (paths.length) await fetchPost('/api/filetree/moveDocs', {fromPaths: paths, toNotebook: otherBox, toPath: '/'}) Defensive patterns
Strategy: validation
Validate before calling
const isTopLevel = p => p.split('/').length <= 2; fromPaths = fromPaths.filter(p => !isTopLevel(p)) Try / catch
try { await fetchPost('/api/filetree/removeDoc', {notebook: boxID, path: p}) } catch (e) { if (String(e).includes('top-level')) { /* skip root doc */ } } Prevention
- Exclude the notebook root document from move/remove batches
- Delete whole notebooks via the notebook API instead of their root doc
- Document that root .sy files are immovable
When it happens
Trigger: moveDocs or removeDoc where a fromPath is the notebook root (IsBoxDocPath true, i.e. the doc at path '/<id>.sy' root level); also triggered by removeDoc on the top-level document.
Common situations: Scripts selecting all docs including the root; UI attempts to delete the first document in a notebook; recursive cleanup jobs starting at '/'.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- Conf.Language(87)
- 106
- 199
- active tab [ ] is not a direct child of [ ]
- AI editor action must not be empty
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/18fd5ca6c08ce73a.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/file.go:1726
// 在移动任何文档前拒绝自身及后代目标,避免调用方将未执行的移动视为成功。
for _, fromPath := range fromPaths {
if fromBox := pathsBoxes[fromPath]; nil != fromBox && fromBox.ID == toBoxID &&
(toPath == fromPath || strings.HasPrefix(toPath, strings.TrimSuffix(fromPath, ".sy")+"/")) {
return errors.New(Conf.Language(87))
}
}
fromPaths = util.FilterMoveDocFromPaths(fromPaths, toPath)
if 1 > len(fromPaths) {
return
}
fromPaths = orderMoveDocPaths(fromPaths, pathsBoxes)
for _, fromPath := range fromPaths {
fromBox := pathsBoxes[fromPath]
if nil != fromBox && IsBoxDocPath(fromBox.ID, fromPath) {
return errors.New(Conf.Language(341))
}
}
if 1 == len(fromPaths) {
// 移动到自己的父文档下的情况相当于不移动,直接返回
if fromBox := pathsBoxes[fromPaths[0]]; nil != fromBox && fromBox.ID == toBoxID {
parentDir := path.Dir(fromPaths[0])
if ("/" == toPath && "/" == parentDir) || (parentDir+".sy" == toPath) {
return
}
}
}
// 检查路径深度是否超过限制
for _, fromPath := range fromPaths {
fromBox := pathsBoxes[fromPath]
childDepth := util.GetChildDocDepth(filepath.Join(util.DataDir, fromBox.ID, fromPath))
if depth := strings.Count(toPath, "/") + childDepth; 6 < depth && !Conf.FileTree.AllowCreateDeeper {View on GitHub (pinned to 9f775e8a12)