gohugoio/hugo · error

failed to copy module to vendor dir

Error message

failed to copy module to vendor dir: %w

What it means

Returned by Client.Vendor when hugio.CopyDir fails copying a mount whose source is a directory into _vendor. Fires after the source Stat succeeded (so the dir exists) but the recursive copy failed. Wrapped with %w.

Solutions

  1. Inspect the wrapped error for the OS-level cause (permission, ENOSPC, symlink).
  2. Fix permissions on the module cache (chmod) and ensure the working dir is writable.
  3. Remove or repair broken symlinks in the offending module directory.
  4. Free disk space or raise tmpfs size if ENOSPC.

Example fix

# before: module cache not readable
sudo chown -R $USER $(go env GOMODCACHE)

# after
hugo mod vendor   # now succeeds
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-check writability of the vendor target.
func vendorDirWritable(dir string) error {
    probe := filepath.Join(dir, ".writeprobe")
    if err := os.MkdirAll(dir, 0o755); err != nil {
        return err
    }
    return os.WriteFile(probe, []byte("x"), 0o644)
}

Try / catch

if err := client.Vendor(); err != nil {
    if strings.Contains(err.Error(), "failed to copy module to vendor dir") {
        // unwrap to inspect permission/space/symlink cause
    }
}

Prevention

When it happens

Trigger: Running `hugo mod vendor` where a mount source is a directory and CopyDir hits a permission error, an unreadable sub-entry, a broken symlink, or the target filesystem (afero.Fs) rejects a write (e.g. read-only overlay, disk full).

Common situations: Filesystem permission issues in the module cache; broken symlinks inside a theme's assets/layouts dir; running vendor on a read-only working directory; CI with a tmpfs that fills up; Windows path-length limits on deeply nested vendor targets.

Related errors


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

Appendix: source

Thrown at modules/client.go:246

		if t.Mounts() == nil {
			return fmt.Errorf("cannot vendor module %q, need at least one mount", t.PathVersionQuery(false))
		}

		fmt.Fprintln(&modulesContent, "# "+t.PathVersionQuery(true)+" "+t.Version())

		dir := t.Dir()

		for _, mount := range t.Mounts() {
			sourceFilename := filepath.Join(dir, mount.Source)
			targetFilename := filepath.Join(vendorDir, t.PathVersionQuery(true), mount.Source)
			fi, err := c.fs.Stat(sourceFilename)
			if err != nil {
				return fmt.Errorf("failed to vendor module: %w", err)
			}

			if fi.IsDir() {
				if err := hugio.CopyDir(c.fs, sourceFilename, targetFilename, nil); err != nil {
					return fmt.Errorf("failed to copy module to vendor dir: %w", err)
				}
			} 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 {

View on GitHub (pinned to 52c9bd7908)