gohugoio/hugo · error
failed to read directory
Error message
failed to read directory %q: %s
What it means
Thrown by os.ReadDir when afero.ReadDir fails on a path that is NOT a not-exist error. Note that genuinely missing directories return (nil, nil) silently; this error fires only for other I/O failures. The %q is the path and %s is the underlying error string.
Solutions
- Confirm the path is actually a directory and not a file (use os.FileExists or check in the shell).
- Check filesystem permissions on the target directory and all parent directories.
- Ensure the path is within the Hugo working directory (workFs = BaseFs.WorkDir).
- Read the %s detail in the error to determine if it is permission-related or an afero internal error.
Example fix
// before:
{{ $files := os.ReadDir "data/config.json" }}
// after (point at the directory, not a file):
{{ $files := os.ReadDir "data" }} Defensive patterns
Strategy: validation
Validate before calling
{{ $info := os.ReadDir $path }}
{{ with $err }}{{ warnf "ReadDir failed: %s" $err }}{{ end }} Prevention
- Verify the path is a directory (not a file) before calling os.ReadDir.
- Keep ReadDir targets inside the Hugo working directory.
- Prefer os.FileExists for single-file checks and use ReadDir only for directories.
When it happens
Trigger: Calling {{ os.ReadDir "some/path" }} in a template where the path exists as a file (not a directory), has restrictive permissions, or triggers an afero filesystem error other than ENOENT.
Common situations: Pointing ReadDir at a file rather than a directory, permission denied on the working directory, a corrupted overlay filesystem entry, or a path that violates Hugo's filesystem sandbox boundaries. The workFs is the WorkDir drop-symlinks filesystem, so symlinks pointing outside are silently dropped rather than erroring here.
Related errors
- invalid filename
- decorate
- template: pattern matches no files: %#q
- walk: Readdir
- abort: jekyll root contains neither posts nor drafts
AI-assisted analysis of gohugoio/hugo@52c9bd7908 (2026-08-09).
Data as JSON: /api/errors/601dbf054c13be89.
Report an issue: GitHub.
Appendix: 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 52c9bd7908)