gohugoio/hugo · error
failed to read dir %q: %q
Error message
failed to read dir %q: %q
What it means
Immediately after opening a module's root directory, `mountCommonJSConfig` (modules/collect.go:679) lists its entries with `ReadDir(-1)` to look for common JS config files and `package.hugo.json`. If the directory listing fails, Hugo reports `failed to read dir %q: %q`. The open succeeded, so this indicates a failure while enumerating entries rather than reaching the path.
Source
Thrown at modules/collect.go:679
func (c *collector) mountCommonJSConfig(owner *moduleAdapter, mounts []Mount) ([]Mount, error) {
for _, m := range mounts {
if strings.HasPrefix(m.Target, files.JsConfigFolderMountPrefix) {
// This follows the convention of the other component types (assets, content, etc.),
// if one or more is specified by the user, we skip the defaults.
// These mounts were added to Hugo in 0.75.
return mounts, nil
}
}
// Mount the common JS config files.
d, err := c.fs.Open(owner.Dir())
if err != nil {
return mounts, fmt.Errorf("failed to open dir %q: %q", owner.Dir(), err)
}
defer d.Close()
fis, err := d.(fs.ReadDirFile).ReadDir(-1)
if err != nil {
return mounts, fmt.Errorf("failed to read dir %q: %q", owner.Dir(), err)
}
hasPackageHugoJSON := false
for _, fi := range fis {
if fi.Name() == files.FilenamePackageHugoJSON {
hasPackageHugoJSON = true
break
}
}
for _, fi := range fis {
n := fi.Name()
should := n == files.FilenamePackageHugoJSON || (n == files.FilenamePackageJSON && !hasPackageHugoJSON)
should = should || commonJSConfigs.MatchString(n)
if should {
mounts = append(mounts, Mount{View on GitHub ↗ (pinned to 8a468df065)
Solutions
- Confirm the quoted path is a real, listable directory: `ls <dir>`.
- Fix module/replacement config if the path resolves to a file instead of a directory.
- On network or containerized filesystems, retry on stable local storage to rule out I/O flakiness.
- If the module cache is corrupted, run `hugo mod clean` and re-fetch modules.
Example fix
# before [module] replacements = "github.com/org/theme -> ./theme.zip" # a file, not a dir # after unzip theme.zip -d ./theme # config: # replacements = "github.com/org/theme -> ./theme"
Defensive patterns
Strategy: try-catch
Validate before calling
if _, err := os.ReadDir(dir); err != nil {
return fmt.Errorf("pre-flight readdir failed for %q: %w", dir, err)
} Type guard
func isReadDirFailure(err error) bool {
var pe *fs.PathError
return errors.As(err, &pe) && pe.Op == "readdirent"
} Try / catch
if err := collect(); err != nil {
var pe *fs.PathError
if errors.As(err, &pe) {
log.Printf("cannot read %s: %v", pe.Path, pe.Err)
}
return err
} Prevention
- Grant read+execute permission on all directories under content/layouts/assets and the module cache
- Watch for directories deleted mid-build (race with file watchers or CI cleanup steps)
- On network filesystems, prefer copying the module cache locally to avoid transient read errors
When it happens
Trigger: `ReadDir(-1)` failing on the module root during collection: I/O errors from the underlying filesystem, the path being a plain file so the afero handle doesn't support directory reads, or the directory being removed between Open and ReadDir.
Common situations: Flaky network filesystems (NFS/SMB/overlay in containers) erroring mid-listing; a replacement or theme path that is actually a file; antivirus or sandboxing software interfering with directory enumeration on Windows; extremely broken symlink farms inside the module dir.
Related errors
- failed to open dir %q: %q
- failed to read archetype file: %w
- %q not found
- failed to open image for decode: %w
- %q is not a directory
AI-assisted analysis of gohugoio/hugo@8a468df065 (2026-07-31).
Data as JSON: /data/errors/3c156b16026cda5e.json.
Report an issue: GitHub ↗.