gohugoio/hugo · error

failed to prune cache

Error message

failed to prune cache %q: %w

What it means

Caches.Prune (filecache_pruner.go:32-48) iterates every named cache and calls Cache.Prune(false). If a cache returns an error that is not a 'file not found' error, it is wrapped with the cache name and propagated. Pruning walks the filesystem removing expired and unused entries; I/O failures surface here.

Solutions

  1. Check and fix read/write/execute permissions on the entire cache directory tree
  2. Ensure no other Hugo process is running and holding locks on cache files
  3. Clear the cache manually (rm -rf the cache dir) and rebuild
  4. Run hugo --gc with the same user/permissions that created the cache

Example fix

# before: cache owned by root, run as normal user
hugo --gc
# after: fix ownership first
sudo chown -R $USER:$USER ~/.cache/hugo && hugo --gc
Defensive patterns

Strategy: try-catch

Validate before calling

// Check cache directory health before running hugo --gc.
func checkCacheTree(dir string) error {
    return filepath.Walk(dir, func(p string, fi os.FileInfo, err error) error {
        if err != nil {
            return err
        }
        return nil
    })
}

Try / catch

// Catch and report the prune error without aborting the whole build.
if n, err := caches.Prune(); err != nil {
    log.Printf("warning: cache prune partially failed: %v (%d items pruned)", err, n)
} else {
    log.Printf("pruned %d cache items", n)
}

Prevention

When it happens

Trigger: Running hugo --gc after a build when the cache filesystem has permission errors, locked files, or I/O failures during the walk and remove operations.

Common situations: Permission issues on cache files created by a different user; files locked by another running Hugo process; disk errors; stale cache from a previous OS/user.

Related errors


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

Appendix: source

Thrown at cache/filecache/filecache_pruner.go:43

	"github.com/spf13/afero"
)

// Prune removes expired and unused items from this cache.
// The last one requires a full build so the cache usage can be tracked.
// Note that we operate directly on the filesystem here, so this is not
// thread safe.
func (c Caches) Prune() (int, error) {
	counter := 0
	for k, cache := range c {
		count, err := cache.Prune(false)

		counter += count

		if err != nil {
			if herrors.IsNotExist(err) {
				continue
			}
			return counter, fmt.Errorf("failed to prune cache %q: %w", k, err)
		}

	}

	return counter, nil
}

// Prune removes expired and unused items from this cache.
// If force is set, everything will be removed not considering expiry time.
func (c *Cache) Prune(force bool) (int, error) {
	if c.cfg.entryIsDir {
		return c.pruneRootDirs(force)
	}
	if err := c.init(); err != nil {
		return 0, err
	}

	counter := 0

View on GitHub (pinned to 52c9bd7908)