vxcontrol/pentagi · warning

%w: resource %q already exists and is not a directory

Error message

%w: resource %q already exists and is not a directory

What it means

ensureResourceDirs builds every missing parent directory row for a target path. If an intermediate path component already exists as a FILE (IsDir=false), a directory cannot be created there: without force it returns errResourceConflict wrapped as 'already exists and is not a directory'; with force it deletes the blocking file instead.

Source

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

		return nil, nil, nil, nil
	}

	parts := strings.Split(dirPath, "/")
	created := make([]models.UserResource, 0, len(parts))
	deleted := make([]models.UserResource, 0, len(parts))
	orphanHashes := make([]string, 0, len(parts))
	current := ""
	for _, part := range parts {
		current = resources.FilePath(current, part)

		existing, exists, err := findResourceByPath(tx, uid, current)
		if err != nil {
			return nil, nil, nil, err
		}
		if exists {
			if !existing.IsDir {
				if !force {
					return nil, nil, nil, fmt.Errorf("%w: resource %q already exists and is not a directory", errResourceConflict, current)
				}
				if err := tx.Delete(&existing).Error; err != nil {
					return nil, nil, nil, fmt.Errorf("failed to delete file blocking directory %q: %w", current, err)
				}
				deleted = append(deleted, existing)
				if existing.Hash != "" {
					orphanHashes = append(orphanHashes, existing.Hash)
				}
			} else {
				continue
			}
		}

		rec := models.UserResource{
			UserID: uid,
			Hash:   "",
			Name:   path.Base(current),
			Path:   current,

View on GitHub (pinned to ea665308ba)

Solutions

  1. Delete or rename the existing file that occupies the directory path, or pass force=true to auto-delete it
  2. Pick a different target path that does not traverse an existing file
  3. List resources to find the offending file at one of the parent segments before uploading

Example fix

// before
upload("reports/2024/q1.csv") // 'reports' exists as a file → conflict
// after
upload("reports/2024/q1.csv?force=true") // deletes blocking file, or:
deleteResource("reports"); upload("reports/2024/q1.csv")
Defensive patterns

Strategy: validation

Validate before calling

// check every parent segment before upload/move/copy
segs := strings.Split(strings.Trim(target, "/"), "/")
cur := ""
for _, s := range segs {
    cur += "/" + s
    if e, ok := lookup(uid, cur); ok && !e.IsDir {
        return fmt.Errorf("%s is a file; delete it or use force", cur)
    }
}

Type guard

func pathClearForDir(entries []Resource, dir string) bool {
    for _, e := range entries {
        if (e.Path == dir || strings.HasPrefix(dir, e.Path+"/")) && !e.IsDir {
            return false
        }
    }
    return true
}

Try / catch

if err := upload(target, r); err != nil {
    if errors.Is(err, errResourceConflict) {
        return fmt.Errorf("a file blocks a directory component of %s; delete it or retry with force", target)
    }
    return err
}

Prevention

When it happens

Trigger: Uploading/moving/copying to path a/b/c when a or a/b is registered as a file, e.g. after a previous upload named 'a' as a file; also hit when path segments collide with an uploaded file's name.

Common situations: Uploading a file named 'data' then later uploading to data/file.txt; directory layouts changed between API versions; scripts creating flat file names that later collide with folder paths.

Related errors


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