gohugoio/hugo · error

decorate: %w

Error message

decorate: %w

What it means

Thrown by baseFileDecoratorFile.ReadDir in hugofs when the per-entry decorator fails while listing a directory. The decorator (set via NewBaseFileDecorator or decorateDirs) attaches metadata such as the real filename and an opener closure to each returned DirEntry; if that closure errors the whole ReadDir aborts. It wraps the underlying decorator error with 'decorate: %w'.

Source

Thrown at hugofs/decorators.go:158

}

func (l *baseFileDecoratorFile) ReadDir(n int) ([]fs.DirEntry, error) {
	fis, err := l.File.(fs.ReadDirFile).ReadDir(-1)
	if err != nil {
		return nil, err
	}

	fisp := make([]fs.DirEntry, len(fis))

	for i, fi := range fis {
		filename := fi.Name()
		if l.Name() != "" {
			filename = filepath.Join(l.Name(), fi.Name())
		}

		fid, err := l.fs.decorate(fi, filename)
		if err != nil {
			return nil, fmt.Errorf("decorate: %w", err)
		}

		fisp[i] = fid.(fs.DirEntry)

	}

	return fisp, err
}

func (l *baseFileDecoratorFile) Readdir(c int) (ofi []os.FileInfo, err error) {
	panic("not supported: Use ReadDir")
}

View on GitHub (pinned to 52c9bd7908)

Solutions

  1. Inspect the wrapped error (errors.Unwrap / %w chain) to find which entry and which decorator op failed.
  2. If using a custom decorator/callback, make its Stat/JoinStatFunc tolerant of missing targets or return the raw entry instead of erroring.
  3. Verify the underlying afero.Fs still resolves the directory's children (call fs.Stat on the failing child path directly).
  4. Ensure no concurrent mutation removes files during the ReadDir iteration (Hugo file watching can rename temp files).

Example fix

// before
decorator := func(fi FileNameIsDir, name string) (FileNameIsDir, error) {
    joined, err := fs.Stat(filepath.Join(base, name))
    if err != nil {
        return nil, err // bubbles up as "decorate: %w"
    }
    return joined, nil
}
// after
decorator := func(fi FileNameIsDir, name string) (FileNameIsDir, error) {
    joined, err := fs.Stat(filepath.Join(base, name))
    if err != nil {
        return fi, nil // fall back to the original entry
    }
    return joined, nil
}
Defensive patterns

Strategy: try-catch

Try / catch

fis, err := decoratedFs.(fs.ReadDirFile).ReadDir(-1)
if err != nil {
    var decErr *fs.PathError
    if errors.As(err, &decErr) || strings.Contains(err.Error(), "decorate:") {
        // fall back to the unwrapped fs to enumerate entries without decoration
        fis, err = unwrap(decoratedFs).ReadDir(-1)
    }
    if err != nil { return err }
}

Prevention

When it happens

Trigger: Calling ReadDir (or Readdir-style traversal) on a filesystem produced by NewBaseFileDecorator whose decorate func returns an error — most commonly because an inner fs.Stat/join in a custom JoinStatFunc failed, or a callback-based decorator rejected an entry. Surfaces indirectly when Hugo walks project/content mounts via hugofs.

Common situations: A custom afero.Fs wrapped by NewBaseFileDecorator whose backing store becomes unavailable (network/overlay mount dropped, permission revoked on a subpath), symlink targets removed mid-build, or a decorator callback that Stat's a joined path that no longer exists. Rare in stock Hugo; common when embedding Hugo's hugofs in another tool with a custom decorator.

Related errors


AI-assisted analysis of gohugoio/hugo@52c9bd7908 (2026-08-09). Data as JSON: /api/errors/e01776c22d4d42be. Report an issue: GitHub.