gohugoio/hugo · error

failed to copy resources to vendor dir

Error message

failed to copy resources to vendor dir: %w

What it means

Returned by Client.Vendor when hugio.CopyDir fails copying the module's 'resources' directory (files.FolderResources) into _vendor, after the directory was confirmed to exist via Stat. Wrapped with %w. This is the post-mounts step that vendors the resource cache folder alongside mounts.

Solutions

  1. Examine the wrapped error for the OS-level cause.
  2. Fix permissions on the module's resources/ directory and re-run.
  3. Remove unneeded contents from resources/_gen before vendoring.
  4. Ensure _vendor is on a writable filesystem with enough space.

Example fix

# before
hugo mod vendor   # CopyDir on resources/ fails (perms)

# after
chmod -R u+r $(go env GOMODCACHE)/<module>@<ver>/resources
hugo mod vendor
Defensive patterns

Strategy: try-catch

Validate before calling

// Check the module's resources dir is readable before vendoring.
func resourcesReadable(fs afero.Fs, dir string) error {
    rd := filepath.Join(dir, "resources")
    if _, err := fs.Stat(rd); err == nil {
        f, err := fs.Open(rd)
        if err != nil {
            return err
        }
        f.Close()
    }
    return nil
}

Try / catch

if err := client.Vendor(); err != nil {
    if strings.Contains(err.Error(), "failed to copy resources to vendor dir") {
        // inspect permissions/symlinks in module's resources/, fix, retry
    }
}

Prevention

When it happens

Trigger: Running `hugo mod vendor` for a module that contains a 'resources/' directory whose recursive copy fails: unreadable files, broken symlinks, permission errors, or a write rejection on the vendor target.

Common situations: Theme modules shipping a resources/_gen image cache with restrictive permissions; broken symlinks in resources; read-only _vendor; large resources dir hitting disk limits on CI; cross-filesystem copy where afero lacks support.

Related errors


AI-assisted analysis of gohugoio/hugo@52c9bd7908 (2026-08-09). Data as JSON: /api/errors/e48b29a964168abd. Report an issue: GitHub.

Appendix: source

Thrown at modules/client.go:266

			} else {
				targetDir := filepath.Dir(targetFilename)

				if err := c.fs.MkdirAll(targetDir, 0o755); err != nil {
					return fmt.Errorf("failed to make target dir: %w", err)
				}

				if err := hugio.CopyFile(c.fs, sourceFilename, targetFilename); err != nil {
					return fmt.Errorf("failed to copy module file to vendor: %w", err)
				}
			}
		}

		// Include the resource cache if present.
		resourcesDir := filepath.Join(dir, files.FolderResources)
		_, err := c.fs.Stat(resourcesDir)
		if err == nil {
			if err := hugio.CopyDir(c.fs, resourcesDir, filepath.Join(vendorDir, t.PathVersionQuery(true), files.FolderResources), nil); err != nil {
				return fmt.Errorf("failed to copy resources to vendor dir: %w", err)
			}
		}

		// Include the config directory if present.
		configDir := filepath.Join(dir, "config")
		_, err = c.fs.Stat(configDir)
		if err == nil {
			if err := hugio.CopyDir(c.fs, configDir, filepath.Join(vendorDir, t.PathVersionQuery(true), "config"), nil); err != nil {
				return fmt.Errorf("failed to copy config dir to vendor dir: %w", err)
			}
		}

		// Also include any theme.toml or config.* or hugo.* files in the root.
		configFiles, _ := afero.Glob(c.fs, filepath.Join(dir, "config.*"))
		configFiles2, _ := afero.Glob(c.fs, filepath.Join(dir, "hugo.*"))
		configFiles = append(configFiles, configFiles2...)
		configFiles = append(configFiles, filepath.Join(dir, "theme.toml"))
		for _, configFile := range configFiles {

View on GitHub (pinned to 52c9bd7908)