thanos-io/thanos · error
creating file watcher
Error message
creating file watcher
What it means
NewConfigWatcher wraps a failure from fsnotify.NewWatcher(), which allocates the OS-level file-watch facility (inotify on Linux). If the watcher cannot be created, Receive cannot observe hashring config changes and setup fails immediately. This is almost always an OS resource-limit problem, not a config-content problem.
Solutions
- Raise the limit: sysctl fs.inotify.max_user_instances=<higher> and fs.inotify.max_user_watches
- Increase the container's file-descriptor limit (ulimit -n / securityContext)
- Reduce other inotify consumers on the node, or restart noisy workloads
- If inotify is unavailable, poll the config file on an interval as a fallback
Example fix
// before (host defaults too low) fs.inotify.max_user_instances = 128 // after sysctl -w fs.inotify.max_user_instances=1024
Defensive patterns
Strategy: retry
Validate before calling
// preflight: check fd headroom
fis, err := os.ReadDir("/proc/self/fd")
if err == nil && len(fis) > rlimNofile*90/100 {
log.Warn("close to fd limit; watcher creation may fail")
} Try / catch
w, err := receive.NewConfigWatcher(logger, path, interval)
if err != nil && strings.Contains(err.Error(), "creating file watcher") {
// raise inotify/ulimit on the host, then retry with backoff
} Prevention
- Raise fs.inotify.max_user_instances/max_user_watches on all nodes
- Set a generous RLIMIT_NOFILE in the container spec
- Avoid running many fsnotify consumers per pod
When it happens
Trigger: Calling NewConfigWatcher when inotify instances are exhausted (fs.inotify.max_user_instances) or the process hit its file-descriptor limit.
Common situations: Kubernetes nodes with many watching pods exhausting max_user_instances; low RLIMIT_NOFILE in the container; running in restricted sandboxes without inotify support.
Related errors
AI-assisted analysis of thanos-io/thanos@35b8b99117 (2026-09-07).
Data as JSON: /api/errors/17ef9de59fd9506e.
Report an issue: GitHub.
Appendix: source
Thrown at pkg/receive/config.go:190
changesCounter prometheus.Counter
errorCounter prometheus.Counter
refreshCounter prometheus.Counter
hashringNodesGauge *prometheus.GaugeVec
hashringTenantsGauge *prometheus.GaugeVec
// lastLoadedConfigHash is the hash of the last successfully loaded configuration.
lastLoadedConfigHash float64
}
// NewConfigWatcher creates a new ConfigWatcher.
func NewConfigWatcher(logger log.Logger, reg prometheus.Registerer, path string, interval model.Duration) (*ConfigWatcher, error) {
if logger == nil {
logger = log.NewNopLogger()
}
watcher, err := fsnotify.NewWatcher()
if err != nil {
return nil, errors.Wrap(err, "creating file watcher")
}
if err := watcher.Add(path); err != nil {
return nil, errors.Wrapf(err, "adding path %s to file watcher", path)
}
c := &ConfigWatcher{
ch: make(chan []HashringConfig),
path: path,
interval: time.Duration(interval),
logger: logger,
watcher: watcher,
hashGauge: promauto.With(reg).NewGauge(
prometheus.GaugeOpts{
Name: "thanos_receive_config_hash",
Help: "Hash of the currently loaded hashring configuration file.",
}),
successGauge: promauto.With(reg).NewGauge(
prometheus.GaugeOpts{View on GitHub (pinned to 35b8b99117)