AdguardTeam/AdGuardHome · warning

unwatching %s %s: %w

Error message

unwatching %s %s: %w

What it means

The default TLS manager watches cert/key files with fsnotify; when rotating certificates it must first unwatch the old paths. appendUnwatchErr collects errors from watcher.Remove — typically fsnotify.ErrNonExistentWatch when the path wasn't actually being watched.

Source

Thrown at internal/aghtls/defaultmanager.go:218

	errs = mgr.appendUnwatchErr(errs, "old key", old.KeyPath)
	errs = mgr.appendWatchErr(errs, "new cert", certKey.CertPath)
	errs = mgr.appendWatchErr(errs, "new key", certKey.KeyPath)

	mgr.pair = certKey

	return errors.Join(errs...)
}

// appendUnwatchErr stops watching a file at path p described by what and
// appends an error to the errs slice, if any.  Empty p is ignored.
func (mgr *DefaultManager) appendUnwatchErr(errs []error, what, p string) (result []error) {
	if p == "" {
		return errs
	}

	err := mgr.watcher.Remove(p)
	if err != nil {
		errs = append(errs, fmt.Errorf("unwatching %s %s: %w", what, p, err))
	}

	return errs
}

// appendWatchErr starts watching a file at path p described by what and
// appends an error to the errs slice, if any.  Empty p is ignored.
func (mgr *DefaultManager) appendWatchErr(errs []error, what, p string) (result []error) {
	if p == "" {
		return errs
	}

	err := mgr.watcher.Add(p)
	if err != nil {
		errs = append(errs, fmt.Errorf("watching %s %s: %w", what, p, err))
	}

	return errs

View on GitHub (pinned to b41aefbe51)

Solutions

  1. Treat this error as non-fatal: it's appended to an error list, so rotation proceeded; check whether the new cert actually loaded
  2. Guard the removal: only call watcher.Remove for paths you successfully added
  3. Upgrade fsnotify if hitting backend quirks; on Linux verify inotify watch limits aren't exceeded
  4. Restart the service to rebuild watcher state cleanly if reloads keep failing

Example fix

// before
errs = appendUnwatchErr(errs, oldPath, "certificate")

// after (defensive, if rolling your own watcher use)
if w, ok := watcher.(*fsnotify.Watcher); ok && isWatched(w, oldPath) {
    _ = watcher.Remove(oldPath)
}
Defensive patterns

Strategy: fallback

Type guard

func isWatchMissing(err error) bool { return errors.Is(err, fsnotify.ErrNonExistentWatch) }

Try / catch

// errors are accumulated, not fatal: inspect the slice
for _, e := range errs {
    if errors.Is(e, fsnotify.ErrNonExistentWatch) { continue } // benign
    log.Warn("tls manager", "err", e)
}

Prevention

When it happens

Trigger: setLocked rotating certificates when the watcher never had a watch on the old path (previous watch failed silently), the watch was already removed, or the file was deleted before unwatch; errors are accumulated into a slice rather than failing the operation.

Common situations: Certificate rotation where the previous cert path changed between reloads; watchers on platforms with flaky fsnotify backends; rapid successive reloads racing watch bookkeeping.

Related errors


AI-assisted analysis of AdguardTeam/AdGuardHome@b41aefbe51 (2026-08-27). Data as JSON: /api/errors/026c026ef63fa004. Report an issue: GitHub.