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
- Verify the path is a directory relative to the project working directory: `os.ReadDir "content/posts"`.
- Check the wrapped underlying error text — 'not a directory' means fix the path; 'permission denied' means fix filesystem permissions.
- 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
- Verify the directory exists with fileExists before calling readDir
- Pass a directory, not a file — os.Stat first if unsure ((os.Stat $p).IsDir)
- Remember readDir resolves relative to the project root, not the current template's directory
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
- invalid filename
- error building site: %w
- failed to detect format from content
- failed to detect target data serialization format
- templates.Defer cannot be used inside a partialCached partia
AI-assisted analysis of gohugoio/hugo@8a468df065 (2026-07-31).
Data as JSON: /data/errors/601dbf054c13be89.json.
Report an issue: GitHub ↗.