{"record":{"id":"ec33475702fbe466","repo":"siyuan-note/siyuan","slug":"move-notebook-s-file-s-failed-s","errorCode":null,"errorMessage":"Move notebook [%s] file [%s] failed: %s","messagePattern":"Move notebook \\[(.+?)\\] file \\[(.+?)\\] failed: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/box.go","lineNumber":465,"sourceCode":"\tIncSync()\n\treturn nil\n}\n\nfunc (box *Box) Move(oldPath, newPath string) error {\n\tif _, err := box.validateBoxPath(oldPath); err != nil {\n\t\treturn err\n\t}\n\tif _, err := box.validateBoxPath(newPath); err != nil {\n\t\treturn err\n\t}\n\tboxLocalPath := filepath.Join(util.DataDir, box.ID)\n\tfromPath := filepath.Join(boxLocalPath, oldPath)\n\ttoPath := filepath.Join(boxLocalPath, newPath)\n\n\tif err := filelock.Rename(fromPath, toPath); err != nil {\n\t\tmsg := fmt.Sprintf(Conf.Language(5), box.Name, fromPath, err)\n\t\tlogging.LogErrorf(\"move [path=%s] in box [%s] failed: %s\", fromPath, box.Name, err)\n\t\treturn errors.New(msg)\n\t}\n\n\tif oldDir := path.Dir(oldPath); ast.IsNodeIDPattern(path.Base(oldDir)) {\n\t\tfromDir := filepath.Join(boxLocalPath, oldDir)\n\t\tif util.IsEmptyDir(fromDir) {\n\t\t\tfilelock.Remove(fromDir)\n\t\t}\n\t}\n\tIncSync()\n\treturn nil\n}\n\nfunc (box *Box) Remove(path string) error {\n\tif _, err := box.validateBoxPath(path); err != nil {\n\t\treturn err\n\t}\n\tboxLocalPath := filepath.Join(util.DataDir, box.ID)\n\tfilePath := filepath.Join(boxLocalPath, path)","sourceCodeStart":447,"sourceCodeEnd":483,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/box.go#L447-L483","documentation":"In Box.Move, after both oldPath and newPath pass validateBoxPath, filelock.Rename(fromPath, toPath) is attempted; on failure it logs and returns errors.New(fmt.Sprintf(Conf.Language(5), box.Name, fromPath, err)) — 'Move notebook [%s] file [%s] failed: %s'. On success it also removes a now-empty ID-named source directory. Rename across filesystems can return EXDEV (cross-device link), and a busy/locked source produces EBUSY on Windows.","triggerScenarios":"POST /api/filetree/moveDocs (and renameDocByID heading2doc/li2doc reshuffles) moving a doc/folder where the target exists, the source is open/locked by another process, the two paths sit on different filesystems (EXDEV), or permissions deny the rename.","commonSituations":"Workspace on one volume with a notebook symlinked to another; target filename already present; the source .sy is held open by a sync/AV process on Windows; moving into a path that was just removed by another client.","solutions":["Ensure source and destination are on the same filesystem (no symlinks splitting the notebook across volumes).","Check the target does not already exist, or overwrite intentionally first.","Close other tools locking the source file (sync clients, editors, AV) and retry.","On EXDEV, fall back to copy-then-delete if a cross-volume move is genuinely required."],"exampleFix":"// before: bare rename across possible volume boundary\nif err := box.Move(oldPath, newPath); err != nil { return err }\n\n// after: detect EXDEV and copy+remove as a fallback\nif err := box.Move(oldPath, newPath); err != nil {\n    if errors.Is(err, syscall.EXDEV) {\n        return copyThenRemove(box, oldPath, newPath)\n    }\n    return err\n}","handlingStrategy":"fallback","validationCode":"// Ensure source and destination share a filesystem before renaming.\nsameFS, _ := sameDevice(fromPath, toPath)\nif !sameFS { /* use copy+remove instead */ }","typeGuard":null,"tryCatchPattern":"// Fall back to copy-then-remove on cross-device rename.\nif err := box.Move(oldPath, newPath); err != nil {\n    if errors.Is(err, syscall.EXDEV) { return copyThenRemove(box, oldPath, newPath) }\n    return err\n}","preventionTips":["Keep a notebook's data on a single filesystem (no symlink to another volume).","Close other tools locking the source file before moving on Windows.","Check the target does not already exist."],"tags":["filesystem","notebook","move","rename"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}