siyuan-note/siyuan · error
Move notebook [%s] file [%s] failed: %s
Error message
Move notebook [%s] file [%s] failed: %s
What it means
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.
Source
Thrown at kernel/model/box.go:465
IncSync()
return nil
}
func (box *Box) Move(oldPath, newPath string) error {
if _, err := box.validateBoxPath(oldPath); err != nil {
return err
}
if _, err := box.validateBoxPath(newPath); err != nil {
return err
}
boxLocalPath := filepath.Join(util.DataDir, box.ID)
fromPath := filepath.Join(boxLocalPath, oldPath)
toPath := filepath.Join(boxLocalPath, newPath)
if err := filelock.Rename(fromPath, toPath); err != nil {
msg := fmt.Sprintf(Conf.Language(5), box.Name, fromPath, err)
logging.LogErrorf("move [path=%s] in box [%s] failed: %s", fromPath, box.Name, err)
return errors.New(msg)
}
if oldDir := path.Dir(oldPath); ast.IsNodeIDPattern(path.Base(oldDir)) {
fromDir := filepath.Join(boxLocalPath, oldDir)
if util.IsEmptyDir(fromDir) {
filelock.Remove(fromDir)
}
}
IncSync()
return nil
}
func (box *Box) Remove(path string) error {
if _, err := box.validateBoxPath(path); err != nil {
return err
}
boxLocalPath := filepath.Join(util.DataDir, box.ID)
filePath := filepath.Join(boxLocalPath, path)View on GitHub (pinned to 251596fc0d)
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.
Example fix
// before: bare rename across possible volume boundary
if err := box.Move(oldPath, newPath); err != nil { return err }
// after: detect EXDEV and copy+remove as a fallback
if err := box.Move(oldPath, newPath); err != nil {
if errors.Is(err, syscall.EXDEV) {
return copyThenRemove(box, oldPath, newPath)
}
return err
} Defensive patterns
Strategy: fallback
Validate before calling
// Ensure source and destination share a filesystem before renaming.
sameFS, _ := sameDevice(fromPath, toPath)
if !sameFS { /* use copy+remove instead */ } Try / catch
// Fall back to copy-then-remove on cross-device rename.
if err := box.Move(oldPath, newPath); err != nil {
if errors.Is(err, syscall.EXDEV) { return copyThenRemove(box, oldPath, newPath) }
return err
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Move notebook [%s] file [%s] failed: %s
- renaming assets in encrypted notebooks is not supported
- prepare box conf [%s] failed: %w
- mkdir box conf dir failed: %w
- write box conf [%s] failed: %w
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/ec33475702fbe466.
Report an issue: GitHub.