gohugoio/hugo · error

is not a directory

Error message

%q is not a directory

What it means

Returned by hugio.CopyDir when the source path exists but fs.Stat reports it as a file rather than a directory. CopyDir walks entries via ReadDir, so a non-directory source is an unrecoverable precondition violation.

Solutions

  1. Point `from` at an actual directory.
  2. If only a single file needs copying, use hugio.CopyFile instead.
  3. Resolve symlinks before calling and verify os.Stat.IsDir().

Example fix

// before
hugio.CopyDir(fs, "content/post.md", "public/post.md", nil)
// after
hugio.CopyFile(fs, "content/post.md", "public/post.md")
Defensive patterns

Strategy: validation

Validate before calling

func ensureDir(fs afero.Fs, p string) error {
  info, err := fs.Stat(p)
  if err != nil { return err }
  if !info.IsDir() { return fmt.Errorf("%q is not a directory", p) }
  return nil
}

Prevention

When it happens

Trigger: Calling hugio.CopyDir(fs, from, to, shouldCopy) where `from` resolves to a regular file, a symlink to a file, or a special file.

Common situations: Theme/static-file copying during `hugo` builds when a configured directory (e.g. `staticDir`, `assetDir`) actually points at a single file; or recursive walks where a symlink unexpectedly resolves to a file.

Related errors


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

Appendix: source

Thrown at common/hugio/copy.go:61

		err = fs.Chmod(to, si.Mode())

		if err != nil {
			return err
		}
	}

	return nil
}

// CopyDir copies a directory.
func CopyDir(fs afero.Fs, from, to string, shouldCopy func(filename string) bool) error {
	fi, err := fs.Stat(from)
	if err != nil {
		return err
	}

	if !fi.IsDir() {
		return fmt.Errorf("%q is not a directory", from)
	}

	err = fs.MkdirAll(to, 0o777) // before umask
	if err != nil {
		return err
	}

	d, err := fs.Open(from)
	if err != nil {
		return err
	}
	entries, _ := d.(iofs.ReadDirFile).ReadDir(-1)
	for _, entry := range entries {
		fromFilename := filepath.Join(from, entry.Name())
		toFilename := filepath.Join(to, entry.Name())
		if entry.IsDir() {
			if shouldCopy != nil && !shouldCopy(fromFilename) {
				continue

View on GitHub (pinned to 52c9bd7908)