vxcontrol/pentagi · warning

%w: cannot copy directory into itself

Error message

%w: cannot copy directory into itself

What it means

copyDirResource guards against copying a directory into itself or one of its descendants, which would be infinitely recursive and corrupt the path tree. It returns errResourceInvalid (wrappable as a 400-class error) when the destination path equals or is nested under the source path.

Source

Thrown at backend/pkg/server/services/resources.go:1564

	entry := convertResource(newRec)
	if exists {
		result.Updated = append(result.Updated, entry)
	} else {
		result.Added = append(result.Added, entry)
	}
	return result, nil
}

func (s *ResourceService) copyDirResource(
	tx *gorm.DB,
	uid uint64,
	srcPath, dstPath string,
	force bool,
) (copyResourceResult, error) {
	result := copyResourceResult{}
	if resources.PathHasPrefix(dstPath, srcPath) {
		return result, fmt.Errorf("%w: cannot copy directory into itself", errResourceInvalid)
	}

	createdRoot, deletedRoot, orphanHashes, err := ensureResourceDirs(tx, uid, dstPath, force)
	if err != nil {
		return result, err
	}
	result.Added = append(result.Added, convertResources(createdRoot)...)
	result.Deleted = append(result.Deleted, convertResources(deletedRoot)...)
	result.OrphanHashes = append(result.OrphanHashes, orphanHashes...)

	dest, destExists, err := findResourceByPath(tx, uid, dstPath)
	if err != nil {
		return result, err
	}
	if destExists && !dest.IsDir {
		return result, errResourceConflict
	}
	if destExists && !force && len(createdRoot) == 0 {

View on GitHub (pinned to ea665308ba)

Solutions

  1. Choose a destination outside the source directory subtree
  2. Validate client-side that dst is not prefixed by src before calling the API
  3. If same-named sibling is wanted, use a path that is not a prefix match (e.g. "/proj-backup" instead of "/proj/backup")

Example fix

// before
copyDir(uid, "/proj", "/proj/backup") // error
// after
copyDir(uid, "/proj", "/backups/proj") // ok
if PathHasPrefix(dst, src) { return ErrInvalidDest }
Defensive patterns

Strategy: validation

Validate before calling

func isCopyIntoSelf(src, dst string) bool {
    return dst == src || strings.HasPrefix(dst, src+"/")
}
if isCopyIntoSelf(srcPath, dstPath) { return errors.New("destination inside source") }

Type guard

func validCopyDest(src, dst string) bool {
    return dst != "" && !strings.HasPrefix(dst, src+"/") && dst != src
}

Try / catch

if err := copyDir(uid, src, dst, force); err != nil {
    if errors.Is(err, errResourceInvalid) {
        return fmt.Errorf("bad destination %q: %w", dst, err)
    }
    return err
}

Prevention

When it happens

Trigger: Calling the copy resource API with dst path == src path, or dst = src + "/sub/...", e.g. copying directory "/proj" to "/proj/backup" or "/a/b" into "/a/b/c".

Common situations: Frontend bug where the destination picker lets the user select the source folder or its child; scripted API calls computing dst by string concatenation without checking containment.

Related errors


AI-assisted analysis of vxcontrol/pentagi@ea665308ba (2026-09-01). Data as JSON: /api/errors/944b01d83ffd7ecd. Report an issue: GitHub.