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 errorView on GitHub ↗ (pinned to 8a468df065)
Solutions
- Usually benign during shutdown — ignore if it appears as `hugo server` exits.
- Restart `hugo server` if the watcher died mid-session and changes stop being picked up.
- On Linux, raise inotify limits (fs.inotify.max_user_watches) so Hugo doesn't fall back to the poller at all.
- 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
- Don't call Add/Remove after Close(); guard shared watchers with a lifecycle flag
- Close the watcher exactly once, at the end of the watch loop
- Treat this error as terminal for the watcher instance — create a new poller if watching must resume
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 ↗.