{"id":"3609ce9aa0dfbcde","repo":"gohugoio/hugo","slug":"errpollerclosed","errorCode":"errPollerClosed","errorMessage":"poller is closed","messagePattern":"poller is closed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"watcher/filenotify/poller.go","lineNumber":17,"sourceCode":"package filenotify\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/fsnotify/fsnotify\"\n\t\"github.com/gohugoio/hugo/common/herrors\"\n)\n\nvar (\n\t// errPollerClosed is returned when the poller is closed\n\terrPollerClosed = errors.New(\"poller is closed\")\n\t// errNoSuchWatch is returned when trying to remove a watch that doesn't exist\n\terrNoSuchWatch = errors.New(\"watch does not exist\")\n)\n\n// filePoller is used to poll files for changes, especially in cases where fsnotify\n// can't be run (e.g. when inotify handles are exhausted)\n// filePoller satisfies the FileWatcher interface\ntype filePoller struct {\n\t// the duration between polls.\n\tinterval time.Duration\n\t// watches is the list of files currently being polled, close the associated channel to stop the watch\n\twatches map[string]struct{}\n\t// Will be closed when done.\n\tdone chan struct{}\n\t// events is the channel to listen to for watch events\n\tevents chan fsnotify.Event\n\t// errors is the channel to listen to for watch errors\n\terrors chan error","sourceCodeStart":1,"sourceCodeEnd":35,"githubUrl":"https://github.com/gohugoio/hugo/blob/8a468df065a75c1c7cf9f6850f32148746590ea5/watcher/filenotify/poller.go#L1-L35","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":null,"typeGuard":"func isPollerClosed(err error) bool { return err != nil && err.Error() == \"poller is closed\" }","tryCatchPattern":"if err := w.Add(path); err != nil {\n    if isPollerClosed(err) { return } // watcher shut down — stop adding paths\n    return err\n}","preventionTips":["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"],"tags":["hugo","file-watcher","polling","lifecycle","hugo-server"],"analyzedSha":"8a468df065a75c1c7cf9f6850f32148746590ea5","analyzedAt":"2026-07-31T21:23:07.045Z","schemaVersion":2}