AlistGo/alist · error
cannot copy parent dir to child
Error message
cannot copy parent dir to child
What it means
The url_tree driver's Copy() applies the same cycle guard as Move: copying a directory into its own subtree would recurse forever when deepCopy walks the source while it is being attached under it, so the operation is refused up front. Identical HasPrefix-based check as the move guard, including its segment-boundary blind spot.
Source
Thrown at drivers/url_tree/driver.go:214
return nil, errs.PermissionDenied
}
d.mutex.Lock()
defer d.mutex.Unlock()
srcNode := GetNodeFromRootByPath(d.root, srcObj.GetPath())
if srcNode == nil {
return nil, errs.ObjectNotFound
}
srcNode.Name = newName
d.updateStorage()
return nodeToObj(srcNode, stdpath.Join(stdpath.Dir(srcObj.GetPath()), newName))
}
func (d *Urls) Copy(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) {
if !d.Writable {
return nil, errs.PermissionDenied
}
if strings.HasPrefix(dstDir.GetPath(), srcObj.GetPath()) {
return nil, errors.New("cannot copy parent dir to child")
}
d.mutex.Lock()
defer d.mutex.Unlock()
dstNode := GetNodeFromRootByPath(d.root, dstDir.GetPath())
if dstNode == nil || dstNode.isFile() {
return nil, errs.NotFolder
}
srcNode := GetNodeFromRootByPath(d.root, srcObj.GetPath())
if srcNode == nil {
return nil, errs.ObjectNotFound
}
newNode := srcNode.deepCopy(dstNode.Level + 1)
dstNode.Children = append(dstNode.Children, newNode)
d.root.calSize()
d.updateStorage()
return nodeToObj(newNode, stdpath.Join(dstDir.GetPath(), stdpath.Base(srcObj.GetPath())))
}
View on GitHub (pinned to 843d9dc814)
Solutions
- Copy to a destination outside the source subtree
- Rename the destination first if it merely shares a name prefix with the source
- Apply the same segment-aware prefix fix as for Move (dst == src || HasPrefix(dst, src+"/")) in a local fork or upstream patch
Example fix
// before
if strings.HasPrefix(dstDir.GetPath(), srcObj.GetPath()) {
return nil, errors.New("cannot copy parent dir to child")
}
// after
src := strings.TrimSuffix(srcObj.GetPath(), "/")
if dstDir.GetPath() == src || strings.HasPrefix(dstDir.GetPath(), src+"/") {
return nil, errors.New("cannot copy parent dir to child")
} Defensive patterns
Strategy: validation
Validate before calling
src := strings.TrimSuffix(srcObj.GetPath(), "/")
if dstDir.GetPath() == src || strings.HasPrefix(dstDir.GetPath(), src+"/") {
return fmt.Errorf("cannot copy %s into its own subtree %s", src, dstDir.GetPath())
} Type guard
func isCopyIntoSelf(src, dst string) bool {
src = strings.TrimSuffix(src, "/")
return dst == src || strings.HasPrefix(dst, src+"/")
} Try / catch
if _, err := d.Copy(ctx, srcObj, dstDir); err != nil {
if strings.Contains(err.Error(), "cannot copy parent dir to child") {
// copy to a sibling outside the subtree, e.g. /backup instead of /music/backup
}
} Prevention
- Pre-validate copy destinations for subtree containment
- Avoid destination names that merely extend the source name ('/data' -> '/data2') given the raw-prefix check
- Guard recursive copy helpers with a depth/visited set as a second line of defense
When it happens
Trigger: Copy(src=/music, dst=/music/backup): destination path starts with source path, rejected. Also (false positive) Copy(src=/music, dst=/music-old) because HasPrefix matches on raw string prefix without requiring a '/' boundary.
Common situations: Attempting to back a folder up into itself via copy; Recursive copy features that keep the destination inside the source tree; Sibling-folder name-prefix collisions triggering the guard unexpectedly
Related errors
- cannot move parent dir to child
- same-name files cannot be copied
- same-name dirs cannot be copied to
- directory separator prohibited
- cannot copy parent dir to child
AI-assisted analysis of AlistGo/alist@843d9dc814 (2026-08-15).
Data as JSON: /api/errors/7176f5668fbc53b2.
Report an issue: GitHub.