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

  1. Re-run the build (transient FS/IO errors often clear); if persistent, investigate the underlying volume.
  2. Ensure no concurrent job removes the module/theme dir during the build.
  3. For network filesystems, check connectivity/mount health and remount if stale.
  4. 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

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)