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 errsView on GitHub (pinned to b41aefbe51)
Solutions
- Treat this error as non-fatal: it's appended to an error list, so rotation proceeded; check whether the new cert actually loaded
- Guard the removal: only call watcher.Remove for paths you successfully added
- Upgrade fsnotify if hitting backend quirks; on Linux verify inotify watch limits aren't exceeded
- 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
- Only unwatch paths you successfully watched
- Rebuild watcher state on service restart after flaky reloads
- Check inotify watch limits (fs.inotify.max_user_watches) on Linux
- Treat unwatch errors as warnings; verify the new cert loaded instead
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
- watching %s %s: %w
- starting watcher: %w
- shutting down watcher: %w
- unknown cipher %q
- parsing tls certificate: %w
AI-assisted analysis of AdguardTeam/AdGuardHome@b41aefbe51 (2026-08-27).
Data as JSON: /api/errors/026c026ef63fa004.
Report an issue: GitHub.