gohugoio/hugo · warning

errPollerClosed

errPollerClosed

Error message

poller is closed

What it means

errPollerClosed (watcher/filenotify/poller.go:17) is returned by Hugo's fallback file poller — used when fsnotify/inotify can't be used — when Add(), Remove(), or Close() is called after the poller was already closed. It guards the closed flag under the mutex so no new watches are registered on a dead poller.

Source

Thrown at watcher/filenotify/poller.go:17

package filenotify

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"sync"
	"time"

	"github.com/fsnotify/fsnotify"
	"github.com/gohugoio/hugo/common/herrors"
)

var (
	// errPollerClosed is returned when the poller is closed
	errPollerClosed = errors.New("poller is closed")
	// errNoSuchWatch is returned when trying to remove a watch that doesn't exist
	errNoSuchWatch = errors.New("watch does not exist")
)

// filePoller is used to poll files for changes, especially in cases where fsnotify
// can't be run (e.g. when inotify handles are exhausted)
// filePoller satisfies the FileWatcher interface
type filePoller struct {
	// the duration between polls.
	interval time.Duration
	// watches is the list of files currently being polled, close the associated channel to stop the watch
	watches map[string]struct{}
	// Will be closed when done.
	done chan struct{}
	// events is the channel to listen to for watch events
	events chan fsnotify.Event
	// errors is the channel to listen to for watch errors
	errors chan error

View on GitHub ↗ (pinned to 8a468df065)

Solutions

  1. Usually benign during shutdown — ignore if it appears as `hugo server` exits.
  2. Restart `hugo server` if the watcher died mid-session and changes stop being picked up.
  3. On Linux, raise inotify limits (fs.inotify.max_user_watches) so Hugo doesn't fall back to the poller at all.
  4. If it recurs during normal operation, report the race to Hugo with logs.
Defensive patterns

Strategy: type-guard

Type guard

func isPollerClosed(err error) bool { return err != nil && err.Error() == "poller is closed" }

Try / catch

if err := w.Add(path); err != nil {
    if isPollerClosed(err) { return } // watcher shut down — stop adding paths
    return err
}

Prevention

When it happens

Trigger: filePoller.Add(name) or Remove after Close() has set w.closed — e.g. watch registration racing with server shutdown or a watcher rebuild while running with --poll or after inotify handle exhaustion forced the polling watcher.

Common situations: Stopping `hugo server` (or a config-triggered watcher restart) while new content directories are still being added to the watch list; environments that force polling (Docker/NFS/WSL mounts, --poll flag, exhausted inotify watches) where teardown races are more visible.

Related errors


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