gohugoio/hugo · warning

watch exists

Error message

watch exists

What it means

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.

Source

Thrown at watcher/filenotify/poller.go:64

	defer w.mu.Unlock()

	if w.closed {
		return errPollerClosed
	}

	item, err := newItemToWatch(name)
	if err != nil {
		return err
	}
	if item.left.FileInfo == nil {
		return os.ErrNotExist
	}

	if w.watches == nil {
		w.watches = make(map[string]struct{})
	}
	if _, exists := w.watches[name]; exists {
		return fmt.Errorf("watch exists")
	}
	w.watches[name] = struct{}{}

	go w.watch(item)
	return nil
}

// Remove stops and removes watch with the specified name
func (w *filePoller) Remove(name string) error {
	w.mu.Lock()
	defer w.mu.Unlock()
	return w.remove(name)
}

func (w *filePoller) remove(name string) error {
	if w.closed {
		return errPollerClosed
	}

View on GitHub ↗ (pinned to 8a468df065)

Solutions

  1. Check whether the path is already watched before calling Add, or treat this error as benign and ignore it (the watch is already active).
  2. Call Remove(name) before re-adding if you intend to reset the watch.
  3. Deduplicate the list of watch paths (mounts, symlink-resolved paths) before registering them.
  4. If seen from hugo server, review config mounts for entries that resolve to the same directory.

Example fix

// before
for _, p := range paths {
    w.Add(p)
}
// after
seen := map[string]bool{}
for _, p := range paths {
    if seen[p] {
        continue
    }
    seen[p] = true
    if err := w.Add(p); err != nil {
        return err
    }
}
Defensive patterns

Strategy: validation

Validate before calling

// Track watched paths yourself before calling Add
if _, ok := watched[path]; ok {
    return nil // already watching
}
if err := poller.Add(path); err == nil {
    watched[path] = struct{}{}
}

Try / catch

if err := poller.Add(path); err != nil {
    if err.Error() == "watch exists" {
        // benign: path already watched, safe to continue
    } else {
        return fmt.Errorf("add watch %q: %w", path, err)
    }
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


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