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

  1. Copy to a destination outside the source subtree
  2. Rename the destination first if it merely shares a name prefix with the source
  3. 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

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


AI-assisted analysis of AlistGo/alist@843d9dc814 (2026-08-15). Data as JSON: /api/errors/7176f5668fbc53b2. Report an issue: GitHub.