gohugoio/hugo · error

failed to read directory %q: %s

Error message

failed to read directory %q: %s

What it means

`os.ReadDir` failed to list the given path for a reason other than non-existence (non-existent paths return nil silently). The underlying afero filesystem error — typically 'not a directory' or a permission error — is wrapped with the path.

Source

Thrown at tpl/os/os.go:125

	if err != nil && herrors.IsNotExist(err) {
		return "", nil
	}
	return s, err
}

// ReadDir lists the directory contents relative to the configured WorkingDir.
func (ns *Namespace) ReadDir(i any) ([]_os.FileInfo, error) {
	path, err := cast.ToStringE(i)
	if err != nil {
		return nil, err
	}

	list, err := afero.ReadDir(ns.workFs, path)
	if err != nil {
		if herrors.IsNotExist(err) {
			return nil, nil
		}
		return nil, fmt.Errorf("failed to read directory %q: %s", path, err)
	}

	return list, nil
}

// FileExists checks whether a file exists under the given path.
func (ns *Namespace) FileExists(i any) (bool, error) {
	path, err := cast.ToStringE(i)
	if err != nil {
		return false, err
	}

	if path == "" {
		return false, nil
	}

	status, err := afero.Exists(ns.readFileFs, path)
	if err != nil {

View on GitHub ↗ (pinned to 8a468df065)

Solutions

  1. Verify the path is a directory relative to the project working directory: `os.ReadDir "content/posts"`.
  2. Check the wrapped underlying error text — 'not a directory' means fix the path; 'permission denied' means fix filesystem permissions.
  3. On CI, ensure the checkout/mount preserves directory permissions and symlinks resolve inside the project.

Example fix

{{/* before */}}
{{ range os.ReadDir "content/posts/first.md" }}...{{ end }}
{{/* after */}}
{{ range os.ReadDir "content/posts" }}...{{ end }}
Defensive patterns

Strategy: try-catch

Validate before calling

{{ if fileExists "static/images" }}
  {{ range readDir "static/images" }}...{{ end }}
{{ end }}

Try / catch

{{ with try (os.ReadDir $dir) }}
  {{ with .Err }}{{ warnf "readDir %q failed: %s" $dir . }}{{ else }}{{ range .Value }}...{{ end }}{{ end }}
{{ end }}

Prevention

When it happens

Trigger: `{{ range os.ReadDir $p }}` where $p points to a regular file instead of a directory, or a directory the Hugo process lacks permission to read; symlink targets outside the allowed filesystem.

Common situations: Passing a file path (e.g. "content/post.md") where a directory is expected; permission problems in CI/containers; symlinked content directories with restrictive security settings; path casing mismatches on case-sensitive filesystems combined with an unexpected file at that name.

Related errors


AI-assisted analysis of gohugoio/hugo@8a468df065 (2026-07-31). Data as JSON: /data/errors/601dbf054c13be89.json. Report an issue: GitHub ↗.