gohugoio/hugo · error
failed to read dir
Error message
failed to read dir %q: %q
What it means
Immediately after opening owner.Dir() succeeds, mountCommonJSConfig calls ReadDir(-1) on it; if reading entries fails (directory deleted mid-call, I/O error, permission revoked between open and read), the raw error is wrapped here. Distinct from 475 in that the directory was openable but unreadable.
Solutions
- Re-run the build (transient FS/IO errors often clear); if persistent, investigate the underlying volume.
- Ensure no concurrent job removes the module/theme dir during the build.
- For network filesystems, check connectivity/mount health and remount if stale.
- Verify directory integrity: `ls -la <dir>`; if it errors, re-fetch the module/theme.
Example fix
// before: stale NFS mount during build // after: remount and re-fetch mount -o remount /srv/modules go clean -modcache && hugo
Defensive patterns
Strategy: retry
Validate before calling
// Read-dir probe with one retry to absorb transient FS errors
func readDirSafe(dir string) ([]os.DirEntry, error) {
entries, err := os.ReadDir(dir)
if err != nil {
time.Sleep(100 * time.Millisecond)
return os.ReadDir(dir)
}
return entries, nil
} Type guard
null
Try / catch
// Treat transient readdir errors as retryable; persistent ones as environment faults.
Prevention
- Don't delete module dirs concurrently with builds.
- Keep network FS mounts healthy; remount stale ones.
- Retry once on transient IO before failing the build.
When it happens
Trigger: The directory fd opened but readdir failed — dir removed between Open and ReadDir (race), filesystem I/O error, NFS/server hiccup, or the fd refers to something that is no longer a readable directory.
Common situations: Concurrent process deleting/renaming the module dir during build, network filesystem transient errors, SELinux denying readdir but allowing open, or a corrupt directory entry.
Related errors
AI-assisted analysis of gohugoio/hugo@52c9bd7908 (2026-08-09).
Data as JSON: /api/errors/3c156b16026cda5e.
Report an issue: GitHub.
Appendix: 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 52c9bd7908)