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

  1. Confirm the quoted path is a real, listable directory: `ls <dir>`.
  2. Fix module/replacement config if the path resolves to a file instead of a directory.
  3. On network or containerized filesystems, retry on stable local storage to rule out I/O flakiness.
  4. 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

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


AI-assisted analysis of gohugoio/hugo@8a468df065 (2026-07-31). Data as JSON: /data/errors/3c156b16026cda5e.json. Report an issue: GitHub ↗.