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
- Choose a destination outside the source directory subtree
- Validate client-side that dst is not prefixed by src before calling the API
- 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
- Validate dst is not equal to or nested under src in the UI and API client
- Never build dst by naive string concatenation of src
- Add unit tests for prefix-edge cases (src=/a, dst=/ab must be allowed)
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
- path must not be empty
- path exceeds maximum allowed length of %d characters
- resource %d not found
- failed to copy resource: %w
- %w: resource %q already exists and is not a directory
AI-assisted analysis of vxcontrol/pentagi@ea665308ba (2026-09-01).
Data as JSON: /api/errors/944b01d83ffd7ecd.
Report an issue: GitHub.