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
- Inspect the wrapped error (errors.Unwrap / %w chain) to find which entry and which decorator op failed.
- If using a custom decorator/callback, make its Stat/JoinStatFunc tolerant of missing targets or return the raw entry instead of erroring.
- Verify the underlying afero.Fs still resolves the directory's children (call fs.Stat on the failing child path directly).
- 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
- Keep decorator closures total: never error on a single bad entry, fall back to the raw entry.
- Do not mutate the underlying filesystem during ReadDir (pause watchers/syncs).
- Pin decorator Stat/JoinStatFuncs to return the original entry on transient errors.
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
- walk: Readdir: %w
- walk: stat: %s
- walk: open: path: %q filename: %q: %s
- failed to read directory %q: %s
- publishDir is empty
AI-assisted analysis of gohugoio/hugo@52c9bd7908 (2026-08-09).
Data as JSON: /api/errors/e01776c22d4d42be.
Report an issue: GitHub.