{"id":"dd2299c32c6399ba","repo":"gohugoio/hugo","slug":"watch-exists","errorCode":null,"errorMessage":"watch exists","messagePattern":"watch exists","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"watcher/filenotify/poller.go","lineNumber":64,"sourceCode":"\tdefer w.mu.Unlock()\n\n\tif w.closed {\n\t\treturn errPollerClosed\n\t}\n\n\titem, err := newItemToWatch(name)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif item.left.FileInfo == nil {\n\t\treturn os.ErrNotExist\n\t}\n\n\tif w.watches == nil {\n\t\tw.watches = make(map[string]struct{})\n\t}\n\tif _, exists := w.watches[name]; exists {\n\t\treturn fmt.Errorf(\"watch exists\")\n\t}\n\tw.watches[name] = struct{}{}\n\n\tgo w.watch(item)\n\treturn nil\n}\n\n// Remove stops and removes watch with the specified name\nfunc (w *filePoller) Remove(name string) error {\n\tw.mu.Lock()\n\tdefer w.mu.Unlock()\n\treturn w.remove(name)\n}\n\nfunc (w *filePoller) remove(name string) error {\n\tif w.closed {\n\t\treturn errPollerClosed\n\t}","sourceCodeStart":46,"sourceCodeEnd":82,"githubUrl":"https://github.com/gohugoio/hugo/blob/8a468df065a75c1c7cf9f6850f32148746590ea5/watcher/filenotify/poller.go#L46-L82","documentation":"Returned by Hugo's polling-based file watcher (watcher/filenotify/poller.go, used when native fsnotify is unavailable, e.g. on NFS or some container mounts) when filePoller.Add is called with a path that is already registered in its watches map. It guards against spawning a duplicate polling goroutine for the same file.","triggerScenarios":"Calling filePoller.Add(name) twice with the identical path string without an intervening Remove. In Hugo this happens when the server's watch setup registers the same directory/file more than once, e.g. overlapping mounts or module mounts resolving to the same path, while running with the poll-based watcher (--poll flag).","commonSituations":"Running `hugo server --poll` with module mounts or symlinks that map the same physical directory into the watch list twice; custom tools embedding hugolib that call Add on an already-watched path; retry logic re-adding a watch after a transient error without removing it first.","solutions":["Check whether the path is already watched before calling Add, or treat this error as benign and ignore it (the watch is already active).","Call Remove(name) before re-adding if you intend to reset the watch.","Deduplicate the list of watch paths (mounts, symlink-resolved paths) before registering them.","If seen from hugo server, review config mounts for entries that resolve to the same directory."],"exampleFix":"// before\nfor _, p := range paths {\n    w.Add(p)\n}\n// after\nseen := map[string]bool{}\nfor _, p := range paths {\n    if seen[p] {\n        continue\n    }\n    seen[p] = true\n    if err := w.Add(p); err != nil {\n        return err\n    }\n}","handlingStrategy":"validation","validationCode":"// Track watched paths yourself before calling Add\nif _, ok := watched[path]; ok {\n    return nil // already watching\n}\nif err := poller.Add(path); err == nil {\n    watched[path] = struct{}{}\n}","typeGuard":null,"tryCatchPattern":"if err := poller.Add(path); err != nil {\n    if err.Error() == \"watch exists\" {\n        // benign: path already watched, safe to continue\n    } else {\n        return fmt.Errorf(\"add watch %q: %w\", path, err)\n    }\n}","preventionTips":["Keep a set of already-watched paths and check membership before calling Add","Deduplicate the path list (filepath.Clean + map) before registering watches","Treat the 'watch exists' error as idempotent-success rather than fatal","Remove the watch before re-adding when re-registering a path after config reload"],"tags":["file-watcher","polling","hugo","duplicate-registration"],"analyzedSha":"8a468df065a75c1c7cf9f6850f32148746590ea5","analyzedAt":"2026-07-31T21:23:07.045Z","schemaVersion":2}